拓展欧几里得算法——青蛙的约会

首先先来介绍一下什么是拓展欧几里得算法!

我们知道欧几里得算法利用gcd(a,b) = gcd(b , a%b)(非重点,可以百度证明过程)来求解a,b的最大公约数

int gcd( int a , int b)

{

  if(b==0) returna;

  else returngcd(b, a%b);

}

那什么是拓展欧几里得呢?和欧几里得算法有什么联系?实际上就是在欧几里得算法求公约数的过程中将x, y求出来!下面来说说x, y是什么鬼!

现在我们知道了 a 和 b 的最大公约数是 gcd ,那么,我们一定!!一定!!(数论知识)能够找到这样的 x 和 y 满足: a*x + b*y = gcd 这是一个不定方程,并且是有多解的。那我们能不能找出一条通式来表达所有的可能解呢?是可以的!只要我们找到一组特殊的解 x0 和 y0 那么,我们就可以用x0 和 y0 表示出整个不定方程的通解:

      x = x0 + (b/gcd)*t  (务必留意)

      y = y0 – (a/gcd)*t

为什么不是:

      x = x0 + b*t

      y = y0 – a*t

  b/gcd 是 b 的因子, a/gcd 是 a 的因子是吧?那么,由于 t的取值范围是整数,你说 (b/gcd)*t 取到的值多还是 b*t 取到的值多?同理,(a/gcd)*t 取到的值多还是 a*gcd 取到的值多?那肯定又要问了,那为什么不是更小的数,非得是 b/gcd 和a/gcd ?

注意到:我们令 B = b/gcd , A = a/gcd , 那么,A 和 B 一定是互素的吧?(仔细想想应该不难理解!)所以最小的系数就是 A 和 B 了!

现在,我们知道了一定存在 x 和 y 使得 : a*x +b*y = gcd , 那么,怎么求出这个特解 x 和 y 呢?只需要在欧几里德算法的基础上加点改动就行了。也就是开头说的求解x, y!

我们观察到:欧几里德算法停止的状态是: a= gcd , b = 0 ,那么,这是否能给我们求解 x y 提供一种思路呢?因为,这时候,只要 a = gcd 的系数是 1 ,那么只要 b 的系数是 0 或者其他值(无所谓是多少,反正任何数乘以 0 都等于 0 但是a 的系数一定要是 1),这时,我们就会有: a*1 + b*0 = gcd

这是最终状态,那我们是否可以从最终状态反推到最初的状态呢?上面不懂为什么没关系!往下看!

假设当前我们要处理的是求出 a 和 b的最大公约数,并求出 x 和 y 使得 a*x + b*y= gcd ,而我们已经求出了下一个状态:b 和 a%b 的最大公约数,并且求出了一组x1 和y1 使得: b*x1 +(a%b)*y1 = gcd , 那么这两个相邻的状态之间是否存在一种关系呢?

我们知道: a%b = a - (a/b)*b(这里的 “/” 指的是整除,例如 5/2=2 , 1/3=0),那么,我们可以进一步得到:

      gcd = b*x1 + (a-(a/b)*b)*y1

          = b*x1 + a*y1 – (a/b)*b*y1

          = a*y1 + b*(x1 – a/b*y1)

对比之前我们的状态:求一组 x 和 y 使得:a*x + b*y= gcd ,是否发现了什么?

这里:

      x = y1

      y = x1 – a/b*y1

那我们怎么知道x1, y1呢?求出x2, y2!!!直到最终状态!

还是不懂具体看看代码,多想想,也许就能懂吧…….

在此之前先来几个定理,实际上就是把上面说的重新简洁地叙述了一遍

定理1 gcd(a,b)是ax+by的线性组合的最小正整数,x,y∈z;
定理2 如果ax+by=c,x,y∈z;则c%gcd==0;
定理3 如果ab是互质的正整数,c是整数,且方程
ax+by=c(1)
有一组整数解x0y0则此方程的一切整数解可以表示为
x=x0+bt;y=y0-at;t∈z;(2)

以上就是扩展欧几里德算法的全部过程,依然用递归写:


扩展欧几里德有什么用处呢?

求解形如 a*x +b*y = c 的通解,但是一般没有谁会无聊到让你写出一串通解出来,都是让你在通解中选出一些特殊的解,比如下面的青蛙约会!

根据题意,两个青蛙跳到同一个点上才算是遇到了,不是一个追上另一个啊!!!所以有 (x+m*t) - (y+n*t) = p * ll  (p是周长,t是跳的次数,lla青蛙跳的圈数跟b青蛙的圈数之差。整个就是路程差等于纬度线周长的整数倍) 这里不懂就代个数看看!这也许就是传说中的抽象和建模吧!难点在这!

转化一下: (n-m)* t + p* ll= x – y;  实际就是求解不定方程a*x+b*y=c!拓展欧几里得!

a = n-m,  b = ll,  c = gcd(a, b),  d =x-y;

a * t + b *  p= d;   (1)

要求的是t的最小整数解。

用扩展的欧几里德求出其中一组解t0,p0, 并令c = gcd(a, b);

a * t0 + b * p0 = c;  (2)

因为c =gcd(a, b), 所以 a * t / c是整数,b * t / c 也是整数,所以 d / c 也需要是整数,否则无解。

(2)式两边都乘(d / c) a * t0 *(d / c) + b* p0 * (d / c) = d;

所以t0 * (d / c)是最小的解,但有可能是负数。

因为a * ( t0 *(d / c) + b*n) + b * (p0 * (d / c) – a*n) = d; (n是自然数) 用到了上面的通式!!!

所以解为 (t0 * (d / c) % b + b) % b;

下面是几个拓展题目:

ZOJ 3609 :http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4712 求最小逆元

ZOJ 3593 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3593 求最小的步数,处理特殊一点就过去了

HDU 1576 http://acm.hdu.edu.cn/showproblem.php?pid=1576 做点处理即可

HDU 2669 http://acm.hdu.edu.cn/showproblem.php?pid=2669 裸的扩展欧几里得

下面给出引用的博客链接:

拓展欧几里得详解:http://m.blog.csdn.net/article/details?id=7786595

大神题解:http://blog.chinaunix.net/uid-22263887-id-1778922.html

#include <iostream>
#define ll long long

using namespace std;

ll x, y;

ll ex_gcd(int a, int b)
{
    if(b == 0){
        x = 1;
        y = 0; ///最终状态来临,我们给出x==1,y==0!回溯开始!
        return a;
    }
    ll ans;
    ans = ex_gcd(b, a%b);  ///一直递归,直到a==gcd,b==0的最终情况
    ll temp = x;
    x = y;
    y = temp - a/b*y;
    return ans;
}

int main( )
{
    ll m, n, L;
    while(cin>>x>>y>>m>>n>>L){
        ll a = n-m;
        ll b = L;
        ll c = x-y;
        ll gcd = ex_gcd(a, b);
        if(c%gcd!=0) {cout<<"Impossible"<<endl;continue;}
        else{
            x*=c/gcd; ///注意了这里我们求出来的是 a*x + b*y = gcd的 x 的最小值,所以要两边乘c/gcd才是真的解
            b/=gcd;   ///b也是一样的道理
            if(b<0) b=-b; 
            ll result = x%b;   ///避免负数情况 (x0 * (c / gcd) % b + b) % b 这才是正解
            if(result<=0) result+=b;
            cout<<result<<endl;
        }
    }
    return 0;
}
发布了35 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Honeycomb_1/article/details/79245525