欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。
基本算法:设a = qb + r
,其中a,b,q,r都是整数,则gcd(a, b) = gcd(b, r)
,即gcd(a, b) = gcd(b, a % b)
。
算法的实现:
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
扩展的欧几里得算法用于计算满足形如a*x+b*y=c
的方程的整数解
首先,我们需要先判断这个方程有没有解
对于形如a*x+b*y=c
的方程,有解的条件是c是a和b最大公约数的倍数
即c = n*gcd(a, b)(n > 0)
在判定有解之后,我们首先需要计算出一组满足条件的(x, y),由于a, b, c都是gcd(a, b)的整数倍,我们可以将它们都缩小gcd(a, b)倍,令A=a/gcd(a,b), B=b/gcd(a,b), C=c/gcd(a,b); 而缩小后的方程与原方程是同解的。
所以原式可以化简为A*x+B*y=C
,其中gcd(A, B) == 1
此时,我们可以先求A*x+B*y=1
的解(x’, y’),然后将其扩大C倍,最后要求的解就是(x, y)=(C*x’, C*y’)
接下来就是研究如何解A*x+B*y=1
即A*x+B*y=gcd(A, B);
假设A>B>0,我们设
A * x[1] + B * y[1] = gcd(A, B);
B * x[2] + (A mod B) * y[2] = gcd(B, A mod B);
而根据欧几里得算法,我们知道gcd(A, B)==gcd(B, A mod B)
所以有下面的推理过程:
A * x[1] + B * y[1] = B * x[2] + (A mod B) * y[2]
=> A * x[1] + B * y[1] = B * x[2] + (A - kB) * y[2] // A = kB + r
=> A * x[1] + B * y[1] = A * y[2] + B * x[2] - kB * y[2]
=> A * x[1] + B * y[1] = A * y[2] + B * (x[2] - ky[2])
=> x[1] = y[2], y[1] = (x[2] - ky[2])
利用这个性质,我们可就以递归的去求解(x,y)了。
其终止条件为B = 0(也就是在递归过程中用gcd求出了最大公约数的时候),此时可以算出对应的(x,y)=(1, 0) 。然后根据递归, 从gcd(d,0)往前处理,在进行欧几里德算法的递归的时候根据相邻两次调用间x, y和x’, y’的关系计算即可求出ax + by = gcd(a, b)的解.
代码如下:
void exgcd(int a, int b, int &x, int &y)
{
if (b == 0)
{
x = 1;
y = 0;
return;
}
int x1, y1;
exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
}
但是上面只是求出了一组满足条件的解(X0, Y0) ,但我们通常都需要用到最小非负的X0,我们又该需要怎么做呢
如果那样的话,我们需要将(A,B,x’,y’)扩充为一个解系。
由于A B是互质的,所以可以将A*x’+B*y’=1扩展为:
AX0+BY0+(u+(-u))AB=1
=> (X0 + uB)*A + (Y0 - uA)*B = 1
=> X = X0 + uB, Y = Y0 - uA
所以可以求得最小的X为(X0 + uB) mod B,(X0 + uB > 0)
同时我们还需要将X扩大C倍,因此最后解为:
X = (X * C) mod B
若x<0,则不断累加B,直到x>0为止。
总体代码如下:
int solve(int a, int b, int c)
{
int d = gcd(a, b);
if (c % d)
{
return -1;
}
a /= d, b /= d, c /= d;
int x, y;
exgcd(a, b, x, y);
x = ((x * c) % b + b) % b;
/*
x = (x * c) % b;
while (x < 0) x += b; //更为通俗的写法
*/
return x;
}
最后提一句,涉及数论的题目一般数据很大,所以一般用long long类型,而不用int型