( 数论专题 )【 扩展欧几里得 】

( 数论专题 )【 扩展欧几里得 】

扩展欧几里德算法

谁是欧几里德?自己百度去

先介绍什么叫做欧几里德算法

有两个数 a b,现在,我们要求 a b 的最大公约数,怎么求?枚举他们的因子?不现实,当 a b 很大的时候,枚举显得那么的naïve ,那怎么做?

欧几里德有个十分又用的定理: gcd(a, b) = gcd(b , a%b) ,这样,我们就可以在几乎是 log 的时间复杂度里求解出来 a 和 b 的最大公约数了,这就是欧几里德算法,用 C++ 语言描述如下:
 

int gcd( int a, int b )
{
    if ( b==0 ) {
        return a;
    }
    return gcd(b,a%b);
}

由于是用递归写的,所以看起来很简洁,也很好记忆。那么什么是扩展欧几里德呢?

现在我们知道了 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 呢?只需要在欧几里德算法的基础上加点改动就行了。

我们观察到:欧几里德算法停止的状态是: 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

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

int ex_gcd( int a, int b, int &x, int &y )
{
    if ( b==0 ) {
        x = 1;
        y = 0;
        return a;
    }
    int re = ex_gcd(b,a%b,y,x);
    y -= x*(a/b);
    return re;
}

依然很简短,相比欧几里德算法,只是多加了几个语句而已。

这就是理论部分,欧几里德算法部分我们好像只能用来求解最大公约数,但是扩展欧几里德算法就不同了,我们既可以求出最大公约数,还可以顺带求解出使得: a*x + b*y = gcd 的通解 x 和 y

 


 

例题1   青蛙的约会   POJ - 1061

题意: 两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。

思路: 设时间为 t 时两青蛙相遇,则两个青蛙的位置分别为(x+mt)mod L(y+nt) mod L,相遇即是(x+mt)%L=(y+nt)%L

(m-n)*t+k*L=y-x   , 式中t为经过的时间,k为多走了几圈。
OK,现在 t和k是未知量, 已经符合ax+by=c的方程了,设a=m-n,b=L,c=y-x,然后套用模板求出ax+by=gcd(a,b)的特解,然后方程两边乘以c/gcd, 这样解出的x也应该乘以c/gcd, 注意t>0,所以要用通解公式得出最小正整数。最后注意用long long~

其中用到了一个公式:

只要我们找到一组特殊的解 x0 和 y0 那么,我们就可以用 x0 和 y0 表示出整个不定方程的通解:

        x = x0 + (b/gcd)*t

        y = y0 – (a/gcd)*t

用这个通解来求出最小时间, 也就是x最小正整数的解。

代码:

#include <iostream>
 
using namespace std;
 
typedef long long ll;
 
ll exgcd( ll a, ll b, ll &x, ll &y ) // 扩展欧几里得模板
{
    if ( b==0 ) {
        x = 1;
        y = 0;
        return a;
    }
    ll re = exgcd(b,a%b,y,x);
    y -= x*(a/b);
    return re;
}
 
int main()
{
    ll x,y,m,n,L;
    cin >> x >> y >> m >> n >> L;
    ll a = m-n;
    ll b = L;
    ll c = y-x;
    if ( a<0 ) { // 最好把方程弄成正的,
        a = -a;  // 方程两边同乘-1
        c = -c;
    }
    ll gcd = exgcd(a,b,x,y);
    if ( c%gcd!=0 ) {  // c不是gcd的倍数,所以 ax+by=c 没有解
        cout << "Impossible" << endl;
    }
    else {
        x = x*c/gcd;   // 把 ax+by=gcd 化成 ( c/gcd )*(ax+by) = c ,特解也乘以倍数
        ll t = b/gcd;   // 通解需要加的倍数, 这个不用乘那个(c/gcd)
        if ( x>=0 ) {
            x = x%t ;
        }
        else {
            x = x%t+t;
        }
        cout << x << endl;
    }
 
    return 0;
}

 

例题2  Romantic   HDU - 2669

题意:给出a,b 求满足 ax+by=1 方程的解( 要求x是最小正整数 )。 根据扩展欧几里得,若gcd(a,b)不为1,该方程一定无解,否则求出特解x0,y0, 根据通解公式求得答案。

        x = x0 + (b/gcd)*t

        y = y0 – (a/gcd)*t


代码:

#include <iostream>
 
using namespace std;
 
int exgcd( int a, int b, int &x, int &y )
{
    if ( b==0 ) {
        x = 1;
        y = 0;
        return a;
    }
    int re = exgcd(b,a%b,y,x);
    y -= x*(a/b);
    return re;
}
 
int main()
{
    int a,b,x,y;
    while ( cin >> a >> b ) {
        int gcd = exgcd(a,b,x,y);
        if ( gcd!=1 ) {  // 如果 a b 的gcd不为1 一定无解
            cout << "sorry" << endl;
        }
        else {
            int t = b/gcd; // x通解需要加的那个倍数
            if ( x>=0 ) {
                int k = x/t; // 需要缩小多少个k
                x = x%t;
                y += k*(a/gcd);
                cout << x << " " << y << endl;
            }
            else {
                int k = x/t+1; // 需要扩大多少个k
                x = x%t + t;
                y -= k*(a/gcd);
                cout << x << " " << y << endl;
            }
        }
    }
 
    return 0;
}

附加一个我对青蛙走路的理解:关于青蛙走路问题的理解( 扩展欧几里得的应用 )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值