看(ACM-ICPC程序设计系列)《数论及应用》第一章,讲到扩展欧几里德算法时,讲到POJ上的一个题目:POJ1061,看了很久也没看懂,可能是数学基础太差了吧!
还好的是我把不懂的地方跳过去了,看到第三章讲同余问题,再次回忆起扩展欧几里德算法了。这次绕不过去了!
首先不懂的就是:a*x ≡ b (mod m) 与ax+my=b的转化。
后来,慢慢看着,觉得应该是这样的:设d=(a*x)%m ;(a*x-d)/m=k1 , (b-d)/m=k2 ;很容易得到这样的等式:a*x-k1*m=b-k2*m,即a*x+(k2-k1)*m=b; 于是,只要设y=k2-k1,就可以得到:ax+my=b这样的二元一次不定方程了。这样一元线性同余方程就转化成了二元一次不等式了!
第二个不懂的就是如何运用扩展欧几里德算法来求任意二元一次不定方程的解。
为了化简问题,我从最简单的地方开始把思路整理一遍。扩展欧几里德算法最原始的形态是这样的:设a,b不全为0,则存在整数x和y,使得:gcd(a,b)=ax+by。 其实这里有一个要求需要注意:gcd(a,b)=1。例如:a=4,b=3,则存在:x=1,y=-1,满足:gcd(a,b)=1=ax+by。假如gcd(a,b)=K怎么求呢?我觉得可以这样做,设x0=x/k,y0=y/k;则ax0+by0=1,求得x0=1,y0=-1,那么实际上,x=k*x0,y=k*y0。例如:a=4,b=3,k=3。则方程为:4x+3y=3。按照上面的思路:很容易得到一个解:x=3,y=-3。
好了,现在到最核心的问题:扩展欧几里德算法内部是怎么运行的?这里也是我为之心碎了好久的地方!
为了更容易理解,还是先举例让自己有个形象的感觉吧!例如:a=225,b=21,则gcd(a,b)=3。那么,这个3如何用225和21的线性组合表示呢?也就是说:225*x+21*y=3,x,y的值可以是多少呢?
我们先来追踪一下用欧几里德算法求225与21的的最大公约数3的过程。225=21*10+15;21=15*1+6;15=2*6+3;6=2*3+0;这几个式子代表了这样的过程:gcd(225,21)=gcd(21,15)=gcd(15,6)!我们再反推过来:gcd(225,21)=3=15-2*6=225-(21*10)-2*(21-15)=225-(21*10)-2*(21-225+21*10)=3*225-32*21;即x=3,y=-32。现在摆在我面前的问题就是怎么用程序实现这个过程了!
- int extend_gcd(int a,int b,int &x,int &y){
- if(b==0){
- x=1;y=0;
- return a;
- }
- int gcd=extend_gcd(b,a%b,x,y);
- int temp=x-a/b*y;
- x=y;
- y=temp;
- return gcd;
- }
这个程序实现的功能就是计算ax+by=gcd(a,b)这样的二元一次不定方程。
最后再来看看poj1061这道题的分析过程吧!
设青蛙一共跳了t步,如果我们把这段路拉成一条直线,则青蛙A离原点:x+m*t;青蛙B离原点:y+n*t:则会有:x+m*t-y-n*t=p*L(p是不定的)!即有:t*(n-m)+p*L=x-y
转化一下,设a=n-m,b=L,c=x-y,则有:a*t+b*p=c,即以t和p为变量的二元一次不定方程。
接下来就是解这个方程了!根据相关的定理:如果c%gcd(a,b)==0,则不定方程有整数解。
由于a*t+b*p=c不一定会满足gcd(a,b)=1,所以需要把方程变形。只需要把方程两边都同时除以gcd(a,b)就可以使新的方程系数满足gcd(a,b)=1,就可以用扩展欧几里德算法来解决问题了!
由于用扩展欧几里德算法计算出的来的x是:ax+by=1的结果,而我们的不定方程是ax+by=c,故x需要赋新的值x=x*c; 很让人烦的是计算到这里还不是最后的结果,因我们求的是最小正整数x,而我们刚才计算出来的不一定是最小值,所以需要这样处理:x=(x%b+b)%b,这样就能保证x是最小的正整数了!
补充一点的是:ax+by=c,最后的结果不只一个,而是一组:x,x+b,x+2*b,…x+(d-1)*b;.
下面把代码贴出来吧!
- #include<stdio.h>
- typedef long long ll;
- ll gcd(ll a,ll b){
- return !b?a:gcd(b,a%b);
- }
- ll extend_gcd(ll m,ll n,ll &x,ll &y){
- if(n==0){
- x=1;y=0;
- return m;
- }else{
- ll g = extend_gcd(n,m%n,x,y);
- ll t=x-m/n*y;
- x=y;y=t;
- return g;
- }
- }
- int main(){
- ll a,b,c,x,y,d;
- ll sx,sy,m,n,L;
- while(~scanf("%I64d %I64d %I64d %I64d %I64d",&sx,&sy,&m,&n,&L)){
- a=n-m;
- b=L;
- c=sx-sy;
- d=gcd(a,b);
- if(c%d==0){
- a=a/d;
- b=b/d;
- c=c/d;
- extend_gcd(a,b,x,y);
- x=c*x;
- x=(x%b+b)%b;
- printf("%I64d\n",x);
- }else{
- printf("Impossible\n");
- }
- }
- return 0;
- }
我个人觉得,代码这样是最容易理解的!
参考博客:
http://blog.csdn.net/zjsxzjb/article/details/6262667
http://huangzhenbc.blog.163.com/blog/static/17997828220112512028548/
本文出自 “每天进步一点点” 博客,请务必保留此出处http://sbp810050504.blog.51cto.com/2799422/1011160