首先,欧几里德算法是什么?又称辗转相除法,用于计算两个正整数a,b的最大公约数。欧几里德算法是基于gcd(a,b)= gcd(b,a%b)这一定理用递归编写的,其中gcd(a,b)即a,b的最大公约数,且可以认为a>b。代码如下:
在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有什么关系呢?
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
= 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;
}
拓展欧几里得算法可以求出二元方程的通解,但是在实际应用中,通常会要求我们求出通解中的特殊值,接下来,通过例题了解一下拓展欧几里得在实际中的应用。
Time Limit: 1000MS | Memory Limit: 10000K |
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。
我们把这两只青蛙分别叫做青蛙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 <cstdio>
#include <iostream>
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;
}