对于a∗x+b∗y=ca*x+b*y=ca∗x+b∗y=c,这样一个二元一次方程组,我们想要得到他的一组解可以用扩展欧几里得算法,参数列表的a,b,x,y就是方程中的a,b,x,y,d计算出来是gcd(a,b)。
算法求出来的是a∗x+b∗y=gcd(a,ba*x+b*y=gcd(a,ba∗x+b∗y=gcd(a,b的一组解,解系可以表示为X=x+b/d∗k,Y=y−a/d∗kX=x+b/d*k,Y=y-a/d*kX=x+b/d∗k,Y=y−a/d∗k,k为任意整数
但是我们要求解的是a∗x+b∗y=ca*x+b*y=ca∗x+b∗y=c
方程组有解的充分必要条件是d∣cd|cd∣c,正确性很容易证明
在确定方程组优解以后c/d∗X,c/d∗Yc/d*X,c/d*Yc/d∗X,c/d∗Y为方程组的一组解系
需要注意的是这样得到的不是方程组的所有的解
例如:对于方程3x+y=603x+y=603x+y=60,17∗3+9∗1=6017*3+9*1=6017∗3+9∗1=60显然为一组解,但是我们却没有办法用过扩展欧几里得算法得到这组解。所以凡是对x,y有限制条件(不是指正负,而是要求和不能超过多少等条件)我们要考虑是否能够使用扩展欧几里得算法,相反,如果对x,y具体大小没有过多限制,而是仅仅说明要求正负等条件时就可以使用扩展欧几里得算法。
void ex_gcd(ll a,ll b,ll &d,ll &x,ll &y)
{if(!b){d=a; x=1; y=0;}else {gcd(b,a%b,d,y,x); y-=x*(a/b);}
}
下面简单证明一下正确性。
令c=a%b=a-k*b (k=a/b)
a∗x1+b∗x2=gcd(a,b)a*x1+b*x2=gcd(a,b)a∗x1+b∗x2=gcd(a,b) b∗x2+c∗y2=gcd(b,c)b*x2+c*y2=gcd(b,c)b∗x2+c∗y2=gcd(b,c)
有最大公因数的性质我们知道gcd(a,b)=gcd(b,c)gcd(a,b)=gcd(b,c)gcd(a,b)=gcd(b,c)
所以我们得到等式a∗x1+b∗y1=b∗x2+c∗y2=b∗x2+a∗y2−k∗b∗y2a*x1+b*y1=b*x2+c*y2=b*x2+a*y2-k*b*y2a∗x1+b∗y1=b∗x2+c∗y2=b∗x2+a∗y2−k∗b∗y2
其中的一组解为x1=y2,y1=x2−k∗y2,其中k=a/bx1=y2,y1=x2-k*y2,其中k=a/bx1=y2,y1=x2−k∗y2,其中k=a/b,因此我们可以递归的求解,终止递归的条件为c==0,则这个时候的b为gcd(a,b),解为x=1,y=0,然后递归的返回(这个时候再看代码应该就能理解了,代码实现很巧妙)
为了快速得到非负的X,可以稍微处理一下:X=(X%B’+B’)%B’,这样得到的就应该是最小的非负X了
(B’=B/D)
简单的例题hihoCoder#1297
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<ctime>
#include<climits>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAXN=1e5+5;ll s1,s2,v1,v2,m;void ex_gcd(ll A,ll B,ll& D,ll& x,ll& y)
{if(!B){D=A; x=1; y=0;}else{ex_gcd(B,A%B,D,y,x);y-=(A/B)*x;}}int main()
{while(~scanf("%lld%lld%lld%lld%lld",&s1,&s2,&v1,&v2,&m)){ll A=v1-v2,B=m,C=s2-s1,D,x,y;if(A<0){A=-A; C=-C;}C=(C%m+m)%m;ex_gcd(A,B,D,x,y);if(C%D){printf("-1\n");continue;}B/=D;x=C/D*x;x=(x%B+B)%B;printf("%lld\n",x);}return 0;
}