2020年2月29日更新:
首先求两数gcd有两种方法:
- 辗转相除法(较常用)
- 更相减损法(有独特的优势但是一般情况下运算较慢)
gcd代码:
1 int gcd(int a,int b){ 2 if(b==0) return a; 3 return gcd(b,a%b); 4 }
众所周知:
lcm(a,b)=a*b/gcd(a,b)
然后我们来解一个如下的方程:
a*x+b*y=c ①
对x,y求解
(已知a,b,c,x,y均为整数,a,b,c已知)
裴蜀定理:
a、b互质的充要条件为存在x,y∈Z,使得a*x+b*y=1
由此我们还可得到一个结论:
如果a*x+b*y=c (已知a,b,c,x,y属于Z,a,b,c已知),则一定有gcd(a,b) | c (这里的‘ | ’表示被整除,左面为小数,如2|6,3|12)
即一定有c为gcd的倍数,才保证此方程有解 ②
如果要解①的方程,就要用到exgcd了!
现在一步步分析:
我们来解①的方程,那么显而易见我们可以有结论②
所以说如果①方程有解,则c可以写成k*gcd(a,b)的形式
然后我们把a的位置代入b,b的位置代入a%b,故gcd(a,b)就变成了gcd(b,a%b)
但由于gcd(b,a%b)=gcd(a,b),所以等式右端恒不变
所以①方程也就变成了求解如下方程:
b*x‘+a%b*y’=c ③
又因为a%b=a-b*(a/b)
所以③方程又可以变成:
b*x‘+(a-b*(a/b))*y’=c ④
然后进行转化就有:
a*y‘+b *(x’-b*(a/b)*y‘)= c ⑤
所以说解方程①就是在解方程⑤,如果说我们已经知道了方程⑤中的一组解x'和y‘,那么我们就知道了方程①一定存在的一组解:x=y',y=(x’-b*(a/b)*y‘).
所以说我们就是在求x'和y'就可以了
然后我们会发现a和b的不断更新就相当于在求gcd的更新的过程,总会有一刻b=0,所以说当b=0时,有a*x=c=k*gcd(a,b),此时x=c/a,并且建议令y=0(原因在后面会讲),然后回溯
所以我们回溯到上一步时,有令上一步的x=y,y=(x’-b*(a/b)*y‘)
重复这一过程,知道回到最初的一步。
解释一下为什么在b==0的时候,建议将y赋值为0
由于在回溯的过程中,x会被赋值为y,y会被赋值为(x’-b*(a/b)*y‘),如果在b==0时y被赋值为其他值,虽然最后也会求出一组解,但是y会滚雪球似的增大,所以很有可能超出最大合法数值。
exgcd代码实现:
1 void exgcd(int a,int b,int &x,int &y){ 2 if(!b){ 3 x=1;y=0;return ; 4 } 5 exgcd(b,a%b,x,y); 6 int temp; 7 temp=x; 8 x=y; 9 y=temp-(a/b)*y; 10 return ; 11 }
然后再讲一下答案的处理:
我们有了a*x+b*y=c ① 时的一组x,y的解
然后我们对①方程进行相应变式:
故我们就有了a*x+b*y+k*a*b-k*a*b=c的一组解
对左侧进行合并:
有 a*(x+k*b)+b*(y-k*a)=c
所以我们对我们求出的x可以进行相应的处理,我们可以对x减去或加上无数的b,我们也得到了另一个x的解
所以当我们需要最小的正整数x解时,我们可得:
1 x=(x%b+b)%b
当其他的情况时,可以进行相应的操作
最后列一下两种exgcd的代码:
- 第一种(真·直译)
1 void exgcd(int a,int b,int &x,int &y){ 2 if(!b){ 3 x=1;y=0;return ; 4 } 5 exgcd(b,a%b,x,y); 6 int temp; 7 temp=x; 8 x=y; 9 y=temp-(a/b)*y; 10 return ; 11 }
- 第二种(用来背的代码;紫书上的,更短更好背,也更容易让人迷糊)
1 void exgcd(int a,int b,int &x,int &y){ 2 if(!b){ 3 x=1;y=0;return ; 4 } 5 exgcd(b,a%b,y,x); 6 y-=(a/b)*x; 7 return ; 8 }
exgcd讲完了,现在开始讲题:
1.洛谷P1082 同余方程
原题:a*x+b*y=1 (显然b为负数)
因为保证有解,所以一定有a,b互质
这就是典型的用exgcd的问题
a,b已知
那么就是求x的最小正整数解了
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int a,b,x,y; 4 void exgcd(int a,int b,int &x,int &y){ 5 if(!b){ 6 x=1;y=0;return ; 7 } 8 exgcd(b,a%b,x,y); 9 int temp; 10 temp=x; 11 x=y; 12 y=temp-(a/b)*y; 13 return ; 14 } 15 int main(){ 16 scanf("%d%d",&a,&b); 17 exgcd(a,b,x,y); 18 printf("%d",(x%b+b)%b); 19 return 0; 20 }
2.lgP1516 青蛙的约会
课件分析:
• 设x0为最终结果,让青蛙相对运动,可列 出式子: • (m - n)x0 = y - x (mod L) • 显然该式可以转化为: • (m - n)x0 + Ly = y - x • 再变换一下变成: • ax + by = c • 变成了熟悉的exgcd问题,正常求解即可。
我个人分析也和课件差不多
代码:(莫名其妙WA掉了#5和#10两个数据)
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 ll x,y,m,n,l; 5 ll nm,xy; 6 ll t,tmp; 7 ll gcd(ll a,ll b){ 8 if(b==0) return a; 9 return gcd(b,a%b); 10 } 11 void exgcd(ll a,ll b,ll &x,ll &y){ 12 if(!b){ 13 x=xy/gcd(n-m,l);y=0;return ; 14 } 15 exgcd(b,a%b,x,y); 16 ll temp; 17 temp=x; 18 x=y; 19 y=temp-(a/b)*y; 20 return ; 21 } 22 int main(){ 23 scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l); 24 nm=n-m;xy=x-y; 25 l=-l; 26 if(xy%gcd(n-m,l)!=0) { 27 printf("Impossible"); 28 return 0; 29 } 30 exgcd(nm,l,t,tmp); 31 printf("%lld",((t%l)+abs(l))%l); 32 return 0; 33 }