扩展欧几里德:
两个整数a,b,他们的最大公因数是gcd(a,b),那么对于ax+by=gcd(a,b)这样的不定方程,存在若干对(x,y)使得等式成立,而扩展欧几里得可以求出其中的一对特解。
Start:
对于一个欧几里得求最大公因数的过程:
比如求 a=12 , b=18 的最大公因数 ( 最大公因数是6 ) :
a b x y
gcd(12 , 18)
= gcd(18 , 12)
= gcd(12 , 6 )
= gcd( 6 , 0 ) 1 0
问题是:求出一对(x,y),使得ax+by=gcd(a,b)成立。
最后一轮辗转相消剩下的两个数是最大公因数和0,然后可以得到这对数的一组特解x=1,y=0,使得等于最大公因数(为什么?非常明显)
然后由底往上回溯,根据[6,0] 的特解(1,0),推出[12,6]的特解,再根据[12,6]的特解推出[18,12]的特解(得到最初想要的结果),以上便是扩展欧几里得的大致过程。
以下便是说明怎么回溯:
gcd(a,b) = ax + by
(辗转相消) 往下递归一步: gcd(a,b) = b(x1) + a%b(y1) (注,x1,x是为了区分,彼此不同)
使用一些小学知识,整理,变形等式:
(a%b本来就是等于a-a/b*b): gcd(a,b) = b(x1) + (a-a/b*b)(y1)
gcd(a,b) = b(x1) + a(y1) - a/b*b(y1)
gcd(a,b) = a(y1) - a/b*b(y1) + b (x1)
gcd(a,b) = a(y1) + b( (x1) - a/b(y1) )
观察第一个式子和最后一个式子可知,x= y1, y= (x1) - a/b*(y1) 。 (红色)
使用本层特解x1,y1,根据上面那个式子推出上一层特解 x,y,即是回溯过程。
演示:
已知最后一层解是:(1,0)
a b x y
gcd(12 , 18)
= gcd(18 , 12)
= gcd(12 , 6 )
= gcd( 6 , 0 ) 1 0
求往上一层,此时x1=1,y1=0。那么 x=y1=0, y= (x1) - a/b*(y1) = 1-12/6*0=1 解(0,1):
a b x y
gcd(12 , 18)
= gcd(18 , 12)
= gcd(12 , 6 ) 0 1
= gcd( 6 , 0 ) 1 0
继续: x1=0,y1=1。那么 x=y1=1, y= (x1) - a/b*(y1) = 0- 18/12*1=-1, 解(1,-1):
a b x y
gcd(12 , 18)
= gcd(18 , 12) 1 -1
= gcd(12 , 6 ) 0 1
= gcd( 6 , 0 ) 1 0
继续: x1=1,y1=1。那么 x=y1= -1, y= (x1) - a/b*(y1) = 1- 12 / 18 * -1 = 1, 解(-1,1):
a b x y
gcd(12 , 18) -1 1
= gcd(18 , 12) 1 -1
= gcd(12 , 6 ) 0 1
= gcd( 6 , 0 ) 1 0
验证:x=-1,y=1,带入 12x+18y=6=gcd(12,18),正确。
实现代码:
#include<bits/stdc++.h>
using namespace std;
int ex_gcd(int a,int b,int &x,int &y){
if(b==0){
x=1,y=0;
return a;
}else{
int x1,y1;
int gcdab=ex_gcd(b,a%b,x1,y1);
x=y1;
y=x1-a/b*y1;
return gcdab;
}
}
int main()
{
int x,y;
int gcdab=ex_gcd(12,18,x,y);
printf("%d,%d,%d\n",x,y,gcdab);
return 0;
}
注:以上使用了x1,y1变量只是为了看得清晰点,实际上可以改成不需要的。
通过一组特解求其他解:
ax+by=gcd(a,b),a项和b项同是加,减ab的最小公倍数等式平衡,并可得其他解。
ax+lcm(a,b)+by-lcm(a,b)=gcd(a,b) -> a(x+lcm(a,b)/a)+b(y-lcm(a,b)/b)=gcd(a,b)
所以,求得一组特解之后,通过x+lcm(a,b)/a ,y-lcm(a,b)/b ,可得其他解。
其中lcm(a,b)为ab的最小公倍数,lcm(a,b)=ab/gcd(a,b) ,
所以也可以表示为:x+b/gcd(a,b) , y - a/gcd(a,b)
求不定方程ax+by=c
扩展欧几里德求得的是a(x1)+b(y1)=gcd(a,b)的特解。而一般来说c是有可能不等于gcd(a,b)的,那么这时两边乘以c/gcd(a,b)就变成ax + by =c 的形式了:
a(x1) * c/gcd(a,b) + b( y1 ) * c/gcd(a,b) = gcd(a,b) * c / gcd(a,b) = c
这里应该注意到:c/gcd(a,b)应该为整数,也就是说c 能够被 gcd(a,b)整除。
x=x1*c/gcd(a,b),y=y1*c/gcd(a,b)
其他组的特解表示为:
求得一组特解x,y 使得ax+by=c成立之后,通过加减gcd(a,b)可以使得 等式仍然成立:ax +gcd(a,b) +by - gcd(a,b) =c,同时使得x,y的增减之后仍是整数:
x=x+b/gcd(a,b) , y=y+a/gcd(a,b)
k表示正整数。
求最小正整数的特解:
形如 : x存在着若干个解:x= x1+k * b/gcd(a,b) ,等差数列?同余原理?这样的式子是存在着最小正整数取值的,
令p=b/gcd(a,b) 。
所以ax+by=c 的 x 的最小正整数解,即是先求出一对特解:x, y.
然后x= (x%p+p)%p ,(从某个起点不断加p,可以从起点x,p,推断出最小正整数的起点)
比如:x=8,p=3,从8开始不断加3,那么最小正起点是(8%3+3)%3= 2 ,
比如:x=-12,p=5,从-12开始不断加5,那么最小正起点是 (-12%5+5)%5= 3
比如:x=8,p=4 (8%4+4)%4=0 ,那么此时x=0,结果应该是x+p=4
(模p意义下x有可能是0,此时得p),取得最小正整数 x
y= ( c - a*x ) / b
#include<bits/stdc++.h>
using namespace std;
int ex_gcd(int a,int b,int &x,int &y){
if(b==0){
x=1,y=0;
return a;
}else{
int gcdab=ex_gcd(b,a%b,y,x);
y-=x*a/b;
return gcdab;
}
}
int getMin_x(int a,int b,int c,int &x,int &y){
int gcdab=ex_gcd(a,b,x,y);
if(c%gcdab==0){
x*=c/gcdab;y*=c/gcdab;
int p=b/gcdab;
x=(x%p+p)%p;
if(x==0)x+=p;
y=(c-a*x)/b;
}
}
int main()
{
int x,y;
int a=12,b=18,c=36; //12x+18y=36
getMin_x(a,b,c,x,y);
printf("%d,%d\n",x,y);
return 0;
}