集训笔记——数论:gcd与exgcd

2020年2月29日更新:

首先求两数gcd有两种方法:

  1. 辗转相除法(较常用)
  2. 更相减损法(有独特的优势但是一般情况下运算较慢)

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 }
原文地址:https://www.cnblogs.com/robertspot/p/12387235.html