一.首先介绍一下什么叫欧几里得算法
欧几里得算法又称为辗转相除法,用于求两个自然数数的最大公约数,若有负数,全变为正数再运算,这里直接给出代码:
(1)非递归版:
int gcd(int a,int b)
{
//return b==0?a:gcd(b,a%b);
if(a
(2)递归版:
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
注:递归版虽然简洁,但是递归容易爆栈,非递归比递归还是要可靠一点虽然很长。
二.拓展欧几里得算法
1.拓展欧几里得是来干啥的:我们用来求不定方程:ax+by = c 的通解或者特解,那为什么叫拓展欧几里得呢?他跟欧几里得算法有什么关系呢?-->当然有关系了,ax+by = c是不一定有解的,但是由于拓展欧几里得定律:对于不完全为0的非负整数a,b,gcd(a, b)表示a, b的最大公约数,必定存在整数对x,y,满足a*x+b*y==gcd(a, b)。 这就是说至少c%gcd(a,b)==0 才能存在x,y解,这也就牵扯到了最大公约数的欧几里得。
2.推一下拓展欧几里得的通解(ax+by = c a,b,c已知)
(1)首先我们从求 ax + by = gcd(a,b)的特解 开始:
已知方程:ax1 + by1 = gcd(a,b) + bx2+(a%b)y2 = gcd(b,a%b);
而我们知道: gcd(a,b) == gcd(b,a%b);
由此得出: ax1 + by1 = bx2 + (a%b)y2;
而: a%b = a - (a/b )* b;
所以: ax1 + by1 = bx2 + [ a - (a/b) * b] y2;
合并得:ax1 + by1 = ay2 + b[ x2 - (a/b) y2];
由恒等关系得: x1 = y2 y1 = x2 - a/b*y2
由此:我们得出了求x1,y1的递归方程,要求ax + by = gcd(a,b)的解x1,y1时,我们需要知道bx + (a%b)y = gcd(b,a%b)的解x2,y2.......依次推到底部,我们发现:
当b==0时 : ax = gac(a,0) 即 ax = a,所以 底部的解为: x = 1,y = 0.然后由此结果回推x1,y1递归求解!
(2)假设上式中我们已经求出了 ax + by = gcd(a,b) 的特解 x1,y1,接下来我们来求 ax + by = c的特解:
<1.>若c%gcd(a,b)! = 0:说明c不是gcd(a,b)的倍数,那么a,b再怎么凑也凑不到c,所以此时无解!
<2.> 若c%gcd(a,b)==0:此时一定有解,而且有多个,所以可求通解!
ax + by = c相当于 ax + by = gcd(a,b)两侧同时乘了一个 c/gcd(a,b)
所以此条件下的特解x0 = x1 * c/gcd(a,b), y0 = y1 * c/gcd(a,b);
(3)最后我们来求ax + by = c的通解(当特解存在时)
我们知道当两项相加时,要想保持和不变,一项变大,另一项就得变小!而这里x总是变化a的倍数,y总是变化b的倍数,要想让他俩变化幅度相同,二者都要变化a,b的最小公倍数倍!
所以:ax = ax + lcm(a,b) by = by - lcm(a,b)
而: lcm(a,b) = a*b/gcd(a,b)
由此: ax = ax + a*b/gcd(a,b)
约掉a得 : x = x + b/gcd(a,b) 即 通解 x = x0 + b/gcd(a,b) *k (k = 0 ,+-1,+-2......)
同理可得 : y = y - a/gcd(a,b) 即 通解y = y0 - a/gcd(a,b) * k (k = 0 ,+-1,+-2......)
3.做题过程
(1)找出题中关系方程:ax + by = c, 递归求解 ax + by = gcd(a,b) 特解x1,y1同时判断 c%gcd(a,b) ==0?继续:无解
(2)求ax + by = c特解 : x0 = x1*c/gcd(a,b), y0 = y1*c/gcd(a,b) ;
(3)求ax + by = c 通解 : x = x0 + b/gcd(a,b)*k y = y0 - b/gcd(a,b)*k ;(有可能找最小正整数解)
注:(1)关键-->找出题目中ax + by = c的关系方程!!!
(2)若题目a,b存在负数,则-ax-by = c ----> a*(-x) + b*(-y) = c化负为正再求解即可
4.代码:
#include
#include
#include
using namespace std;
int ex_gcd(int aa,int bb,int &xx,int &yy)//递归
{
if(bb==0){xx = 1; yy = 0;return aa;}
int ans = ex_gcd(bb,aa%bb,xx,yy);
int temp = xx;
xx = yy;
yy = temp - aa/bb*yy;
return ans;
}
int main()
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
int x,y;//求第一个特解
int res = ex_gcd(a,b,x,y);
if(c%res)printf("Impossible\n");
else{
int x0 = x * c/res;//求第二个特解
int y0 = y * c/res;
int L = b/res;
if(L<0)L = -L;
int X = (x0%L + L)%L;//求x通解里的最小正整数
int Y = (c - a*X)/b;
printf("x最小正整数解时 : x = %d,y = %d\n",X,Y);
}
return 0;
}
5.例题:
(1)Poj 1601 青蛙的约会
分析: 两只青蛙要想碰面,必须在某一时刻落在同一点: x + t*m = y + t*n + k*L
合并得 : (x -y) = t*(n - m) + k*L ---->a = n-m, x = t, b = L,y = k,c = x-y;求解该拓展欧几里得的最小正整数解即可
代码:
#include
#include
#include
using namespace std;
typedef long long int LL;
LL ex_gcd(LL a,LL b,LL &xx,LL &yy)
{
if(b==0){xx = 1;yy = 0;return a;}
LL d = ex_gcd(b,a%b,xx,yy);
LL temp = xx;
xx = yy;
yy = temp - a/b*yy;
return d;
}
int main()
{
LL x,y,m,n,L;
scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&L);
LL X,Y;
LL k = ex_gcd(n-m,L,X,Y);
if((x-y)%k)printf("Impossible\n");
else{
LL fx = (x-y)/k*X;
L/=k;
if(L<0)L = -L;
while(fx<0)fx+=L;
printf("%lld\n",fx%L);
}
return 0;
}
(2)HDU 2669 (拓展欧几里得裸题)
#include
#include
#include
#include
using namespace std;
typedef long long int LL;
LL ex_gcd(LL aa,LL bb,LL &xx,LL &yy)
{
if(bb==0){xx = 1;yy = 0;return aa;}
LL res = ex_gcd(bb,aa%bb,xx,yy);
LL temp = xx;
xx = yy;
yy = temp - aa/bb*yy;
return res;
}
int main()
{
LL a,b;
while(cin>>a>>b){
LL x,y;
LL ans = ex_gcd(a,b,x,y);
if(ans!=1)printf("sorry\n");
else{
LL L = b;
LL X = (x%b + b)%b;
LL Y = (1 - a*X)/b;
cout<