目录
1.什么是gcd算法,什么又是扩展gcd?
首先gcd算法是一个很古老的用于计算两数最大公约数的算法,而扩展gcd是基于gcd的一个扩展算法,用于求解模线性方程ax≡b (mod n).
2.gcd和扩展gcd有什么用
(1).gcd
gcd用于求两数最大公约数是用到了一个定理
gcd(a,b)=gcd(b,a%b)
这个定理的证明可参阅算法导论(第二版) P526 ,这里就不做证明了.
由于这个定理的成立,b是不断减小的终会趋向于0,故而有如下算法:
int gcd(int a.int b){ //返回a,b的最大公因数
if(b==0) return a;
return gcd(b,a%b);
}
当然也可使用递归实现:
int gcd(int a,int b){ //返回a,b的最大公因数
while(b){
int t=a;
a=b;
b=a%b;
}
return a;
}
这里顺便提一个小细节,a与b的最大公倍数可以通过a*b/gcd(a,b)得到.
(2).扩展gcd
然后是扩展gcd,当我们已知正整数a,b,c,想要求得满足ax+by=c的一组整数x,y时,就可以使用扩展gcd,因为当我们试图求ax+by=c时,若c不是gcd(a,b)的倍数,答案是肯定不存在的,因为毕竟在等式成立的情况下c是有一定数目的ab加起来得到的,自然ab的最大公因数也应该是c的因数.
故而我们可以通过求得满足ax+by=gcd(a,b)的解tx,ty然后满足ax+by=c的解答自然就是
x=tx*(c/gcd(a,b)),y=ty*(c/gcd(a,b));
现在我们需要想办法求出满足ax+by=gcd(a,b)的x,y为多少?
我们会发现当普通的gcd循环到底层的时候gcd(a,b)=a,此时x,y的值固定为1,0;
故而我们可以在gcd返回的同时返回x,y,并且调整x,y为当前层正确的值就好,最终返回到顶层的x,y就是我们要的答案.
最大的难点其实在于下层返回给上层的xy是建立与下层的ab得到的答案,我们怎么以之得到上层的a,b下的x,y,如下
假设在第二层b2为0了,递归开始向上返回
第一层 a1x1+b1y1=gcd(a1,b1)
第二层 a2x2+b2y2=gcd(a2,b2)
因gcd过程中gcd(a1,b1)=gcd(a2,b2),
故而 a1*x1+b1*y1=a2*x2+b2*y2
又因 a2=b1, b2=a1%b1=a1-a1/b1*b1
故而 a1*x1+b1*y1=b1*x2+(a1-a1/b1*b1)*y2 -> a1*x1+b1*y1=a1*y2+b1*(x2-a1/b1*y2)
故而 x1=y2,y1=x2-a1/b1*y2 时第一层的等式也成立,由此可以以第二层返回的x2,y2推出上一层的x1,y1
思路就是这样子
扩展gcd模版代码如下:
ll extgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1;
y=0;
return a;
}
ll d=extgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-a/b*y;
return d;
}
针对与求解ax+by=c,还得在扩展gcd后处理一下x避免不存在解或解x为负数
ll cal(ll a,ll b,ll c){ //计算ax+by=c的满足条件的x
ll x,y;
ll gcd=extgcd(a,b,x,y);
if(c%gcd!=0) return -1; //不存在解
x*=c/gcd;
b/=gcd;
if(b<0) b=-b;
ll ans=x%b;
if(ans<=0) ans+=b; //对ans为负的特殊处理+
return ans;
}
3.例题:
1.poj1061 青蛙的约会
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 126336 | Accepted: 27209 |
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
题意分析:
1.题是什么?
世界被看做一条长度为L的首尾相接的线,两只青蛙分别在x,y位置,他们一起向西跳,每步分别跳m,n,跳是同步的,问在多少次跳跃后相遇,不能就impossible.
2.思路
青蛙相距距离知道了为x-y,mod也知道了是赤道长度L,因为两个青蛙跳的速度一样故而可以认为每个时间步移动m-n,故而其实就是求一个线性同余方程 (m-n)*ans=(x-y) mod L,看是否存在ans满足答案.
3.小细节
求完答案之后为了避免ans是负数一定要记得正常情况下要调用cal函数后面那部分去再处理扩展gcd的结果.
ac代码
#include <stdio.h>
typedef long long ll;
ll extgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1;
y=0;
return a;
}
ll d=extgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-a/b*y;
return d;
}
ll cal(ll a,ll b,ll c){ //计算ax+by=c的满足条件的x
ll x,y;
ll gcd=extgcd(a,b,x,y);
if(c%gcd!=0) return -1; //不存在解
x*=c/gcd;
b/=gcd;
if(b<0) b=-b;
ll ans=x%b;
if(ans<=0) ans+=b; //对ans为负的特殊处理+
return ans;
}
void solve(){
//两只青蛙能跳到一起 -> (m-n)ans=ty-tx+lk -> (m-n)ans+lk=ty-tx ans是答案,k是整数
ll tx,ty,m,n,l;
while(scanf("%lld%lld%lld%lld%lld",&tx,&ty,&m,&n,&l)!=EOF){
ll res=cal(m-n,l,ty-tx); //传入abc
if(res==-1){
printf("Impossible\n");
continue;
}
else printf("%lld\n",res);
}
}
int main(){
solve();
return 0;
}