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

        首先,欧几里德算法是什么?又称辗转相除法,用于计算两个正整数a,b的最大公约数。欧几里德算法是基于gcd(a,b)= gcd(b,a%b)这一定理用递归编写的,其中gcd(a,b)即a,b的最大公约数,且可以认为a>b。代码如下:
long long gcd( long long x, long long y )
{
    if( y== 0 )
        return x;
    return gcd( y, x% y );
}

        在gcd(y,x%y)递归的最后一步中,设c=x%y,一定会有 c是y的约数,即y%c==0。在此,对于c 有c==1和c!=1两种情况,(若c==1,说明x,y互质),但无论那种情况,最终都会得到y==0。例如gcd(8,6),递推的最后一步是gcd(6,2)(c!=1),又例如gcd(5,7),最后一步是gcd(2,1)(c==1)。
        gcd(a,b)= gcd(b,a%b)如何理解呢?假设d为a,b的最大公约数,那么d是a-b,a-2b,...,a-n*b(即a%b)的约数。

        拓展欧几里得算法又是什么呢?它是用来求解已知a,b的二元方程ax+by=gcd(a,b)的算法。
        我们知道,ax+by=gcd是一个不定方程,一定存在多解,但是我们只要求出一组解x0,y0,就可以表示出整个方程的通解:
        x=x0+(b/gcd)*t,
        y=y0-(a/gcd)*t
        (证明:a(x-(b/gcd)*t)+b(y+(a/gcd)*t)=gcd)。
        现在我们思考,如何求解x0,y0呢?在欧几里德算法的最后一步递归中,a=gcd,b=0,即a+b=gcd。那么,是否有一种处理能将欧几里得算法的思路用于求解二元方程呢?只要a的系数为1,b的系数为0或其它任何值,就有a*1+b*0=gcd(x=1,y=0)。当然,这是运算的最终结果,通过逆推,我们可以得到方程的最初形式ax+by=gcd(本式的a,b与上式不同,本式中的a,b经过gcd运算,在递推的最后一步中得到的a,b为上式中的a,b),而在第一次递归中b*x1+(a%b)*y1=gcd,那么两式中x与x1,y与y1有什么关系呢?

        我们知道a%b = a - (a/b)*b(这里的”a/b“是指整除,例如5/2=2),那么:

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

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

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

        由此,我们得到x=y1,y=x1-a/b*y1.

        拓展欧几里德算法也可以用递归编写:

void exgcd( long long a, long long b, long long &x, long long &y )
{
    if( b== 0 )
    {
        x= 1;
        y= 0;
        return;
    }
    exgcd( b, a% b, x, y );
    long long t= x;
    x= y;
    y= t- a/ b* y;
    return;
}

        拓展欧几里得算法可以求出二元方程的通解,但是在实际应用中,通常会要求我们求出通解中的特殊值,接下来,通过例题了解一下拓展欧几里得在实际中的应用。

poj 1061 青蛙的约会
Time Limit: 1000MS  

Memory Limit: 10000K

Description

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

Input

输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。

Output

输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行"Impossible"

Sample Input

1 2 3 4 5

Sample Output

4


        结合题意,我们可以得到(m-n)*p-L*q=x-y,设m-n=a,L=b,x-y=c,得a*p-b*q=c。判断两只青蛙能否碰面,即方程是否有解的条件是c%gcd(a,b)==0,也就是说,a,b的最大公约数也是c的约数。
        然后,为了方便使用拓展欧几里得算法,方程两边同除以gcd(a,b),即a=a/gcd,b=b/gcd,c=c/gcd,然后调用函数exgcd(),得到p,也就是x0,但是,注意,此时我们得到的是a*p-b*q=1的解(此时a,b互质,gcd(a,b)=1),所以,a*p-b*q=c的解应该是p=p*c。最小解就是p%b。
        此外,在实际运算中,m-n,x-y以及我们得到的结果p都有可能是负值,但是在计算机运算中,一个负值p取b的模还是负值,因此,我们只要在p上再加上一个b使p>0即可。
        最后,附上AC代码:
#include 
#include 
using namespace std;

long long gcd( long long x, long long y )
{
    if( y== 0 )
    {
        return x;
    }
    return gcd( y, x% y );
}

void exgcd( long long a, long long b, long long &x, long long &y )
{
    if( b== 0 )
    {
        x= 1;
        y= 0;
        return;
    }
    exgcd( b, a% b, x, y );
    long long t= x;
    x= y;
    y= t- a/ b* y;
    return;
}

int main()
{
    long long x, y, m, n, l;
    while( scanf( "%lld %lld %lld %lld %lld", &x, &y, &m, &n, &l )!= EOF )
    {
        long long a= n- m, b= l, c= x- y, p, q;
        long long d= gcd( a, b );
        if( c% d )
        {
            puts( "Impossible" );
            continue;
        }
        a/= d, b/= d, c/= d;
        exgcd( a, b, p, q );
        p*= c;
        long long ans= p% b;
        while( ans< 0 )
        {
            ans+= b;
        }
        printf( "%lld\n", ans);
    }
    return 0;
}

你可能感兴趣的:(拓展欧几里得算法 poj 1061 青蛙的约会)