欧几里德算法
求最大公约数
命题:设a % b = r,求证:gcd(a, b) = gcd(b, r)。
由a % b = r,可令a = bq + r。
设u是a和b的公约数,则有u|a且u|b,那么u|(a-bq),即u|r,可见u也是b与r的公约数。
反过来,设v是b和r的公约数,则有v|b且v|r,那么v|(bq+r),即v|a,可见v也是a与b的公约数。
综上,a与b的公约数集合跟b与r的公约数集合是相同的,自然最大公约数也相等。
利用上述结论可在对数时间内求出两数的最大公约数。
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
拓展:
- 根据A * B = gcd(A, B) * lcm(A, B),可以求出两数的最小公倍数。
- 求多个数的最大公约数可以迭代求,例如要求A,B,C的最大公约数,先求A与B的最大公约数d=gcd(A, B),再求d与C的最大公约数x=gcd(d, C),那么x为A,B,C的最大公约数。
- 求多个数的最小公倍数可用同样的方式迭代求解。
解二元一次方程
二元一次方程AX + BY = C有整数解的充要条件是gcd(A,B)|C。不失一般性,给定A和B,如何求整数X与Y满足:AX + BY = gcd(A,B)?
以下是通过xgcd过程求解的伪代码:
int xgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int d = xgcd(b, a%b, y, x);
y -= a / b * x;
return d;
}
以a=99, b=78为例,手工计算过程如下:
99 = 78 * 1 + 21
78 = 21 * 3 + 15
21 = 15 * 1 + 6
15 = 6 * 2 + 3
6 = 3 * 2
=> 3 = 15 - 6 * 2 = 15 - (21 - 15) * 2 = 15 * 3 - 21 * 2
= (78 - 21 * 3) * 3 - 21 * 2
= 78 * 3 - 21 * 11
= 78 * 3 - (99 - 78) * 11
= 99 * (-11) + 78 * 14
有些时候,需要求的是某个变量的最小正整数解,该怎么办呢?不妨设二元一次方程为:AX + BY = C,求最小的正整数解X。
- 记d=gcd(A, B),如果d|C,那么解存在,否则无解。
- 等式两边除以d,约去公因子,A'X + B'Y = C',该方程与原方程同解,此时必有gcd(A', B') = 1。
- 根据扩展欧几里德求出方程A'X + B'Y = 1的一组解(x, y),那么对应原方程的一组解为(X, Y) = (C'x, C'y)。
- 最小的正解为X = X mod B,如果此时X为负,用X = X + abs(B)做下调整。
求解模线性方程
考虑求方程:ax = b(mod n),其中a>0, n>0,求所有满足条件的x的值。
由ax = b(mod n),得ax = kn + b,也就是ax + kn = b,显然用扩展欧几里德算法求解。
相关定理:
- 方程ax = b(mod n)对于未知量x有解,当且仅当gcd(a, n) | b。
- 方程ax = b(mod n)要么无解,要么对模n有d个不同的解,其中d = gcd(a, n)。
- 设d = gcd(a, n),假定对整数x'和y',有d = ax' + ny',如果d | b,则方程ax = b(mod n)有一个解为x0,满足x0 = x' * b / d mod n。
- 假设方程ax = b(mod n)有解,x0是该方程的任意一个解,则该方程对模n恰有d个不同的解,分别为:x[i] = x0 + i * n / d,其中i = 0,1,2,…,d-1。
以下是求解的伪代码:
function modular_linear(a, b, n)
(d, x', y') <- xgcd(a, n)
if d | b
x0 <- x'(b/d) mod n
for i <- 0 to d-1
print (x0 + i(n/d)) mod n
else
print "no solution"
以方程14x = 30(mod 100)为例,a=14,b=30,n=100,调用xgcd后(d, x, y) = (2, -7, 1),由于2|30,所以有2个解,x0 = -7 * 15 mod 100 = 95,x1 = (95 + 50) mod 100 = 45。
乘法逆元
考虑计算(a / b) mod n,如果计算a/b比较麻烦,由于式子中含有除法,而除法取余不满足分配率,若能将除法转化为乘法就好了,因为乘法取余满足:(x * y) mod z = ((x mod z) * (y mod z)) mod z。下面试图找一个数r,使得(a / b) mod n = (a * r) mod n恒成立,将问题转化。
在上一节求解模线性方程的结论中,当a与n互质时,gcd(a,n)=1,方程ax=b(mod n)有唯一解。特别地,如果b=1,方程变为ax=1(mod n),满足该条件的x称为a的模n乘法逆元。
可以证明,我们需要寻找的数r,其实就是b的模n乘法逆元。
调用一次扩展欧几里德:(1, x, y) <- xgcd(b, n),即bx+ny=1,由于bx mod n = (-yn + 1) mod n = 1,因此x是b的模n乘法逆元。由于一般要求乘法逆元非负,所以x为负数时要将它转成正数。
例题:求乘法逆元
def xgcd(a, b):
if b == 0:
return (a, 1, 0)
(d, x, y) = xgcd(b, a % b)
xx, yy = y, x - a / b * y
return (d, xx, yy)
m, n = map(int, raw_input().split())
(d, x, y) = xgcd(m, n)
print (x - x / n * n + n) % n
解模线性方程组
设有模线性方程组:x = r[i] (mod m[i]),其中i=1,2,…,n,求满足条件的最小正整数x。
先考虑只有两个的情况:
x = r[1] (mod m[1])
x = r[2] (mod m[2])
=> x = r[1] + k[1]m[1] = r[2] + k[2]m[2]
=> k[1]m[1] - k[2]m[2] = r[2] - r[1]
设d=gcd(k[1], k[2]),如果r[2]-r[1]不能被d整除,则方程组无解。
否则方程两边同时除以d,记A=m[1]/d,B=m[2]/d,C=(r[2]-r[1])/d,则化为
Ak[1] - Bk[2] = C,且gcd(A, B) = 1。
调xgcd(A, B, u0, v0)求出方程Ak[1] - Bk[2] = 1的一组解,
那么(u, v) = (Cu0, Cv0)是方程Ak[1] - Bk[2] = C的一组解。
将u=k[1]代回x = r[1] + k[1]m[1]得到方程组的一个特解,记为x0。
那么方程组的通解可表示为:x = x0 + t * lcm(m[1], m[2]),其是t为任意整数。
该式子等价于:x = x0 (mod lcm(m[1], m[2])),相当于把最初的两个方程合并了。
一直合并下去,到只剩下一个方程为止,那么此时的x0即为答案。
参考代码:
#include <iostream>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
void xgcd(LL a, LL b, LL &x, LL &y) {
if (!b) {
x = 1; y = 0;
} else {
xgcd(b, a % b, y, x);
y -= a / b * x;
}
}
int main() {
LL n, m[1005], r[1005], M, R, d, u, v, A, B, C;
cin >> n;
for (int i = 0; i < n; i++)
cin >> m[i] >> r[i];
M = m[0]; R = r[0];
for (int i = 1; i < n; i++) {
d = gcd(M, m[i]);
if ((r[i] - R) % d) {
R = -1;
break;
}
A = M / d; B = m[i] / d; C = (r[i] - R) / d;
xgcd(A, B, u, v);
u = u * C % B;
R = R + u * M;
M = M * B;
R = R % M;
if (R < 0) R += M;
}
cout << R << endl;
return 0;
}