1.扩展欧几里得算法
回忆:求最大公约数中学过欧几里得算法(辗转相除法):gcd(a,b) == gcd(b, a % b);
裴蜀定理:对于任意正整数a,b,那么一定存在非零整数x,y,使得ax+by=gcd(a,b);
扩展欧几里得算法:就是求解上面裴蜀定理中a和b的系数x,y;具体怎么求?
- 首先若b=0,那么显然gcd(a,0)=a,则可得x=1,y=0;
- 若a,b都不为0,结合欧几里得算法
gcd(a,b) == gcd(b, a % b)
,那么ax+by=gcd(a,b)
===>b*x+(a%b)*y=gcd(a,b)
,但在递归的过程中a的系数由x变为y,b的系数由y变为x,所以在递归过程的传入参数时因调换x和y的位置,变为b*y+(a%b)*x=gcd(a,b)
,下面的代码会体现; - 设gcd(a,b)=d,那么继续推理得到
即可求出系数,注意x和y可能不是唯一的。
Acwing 877.扩展欧几里得算法
具体实现代码(详解版):
#include <iostream>
using namespace std;
// 扩展欧几里得算法函数
int exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1; // 基本情况:如果 b == 0,x 为 1
y = 0; // y 为 0,因为 a * 1 + b * 0 = a
return a; // 返回 gcd(a, b)
}
int d = exgcd(b, a % b, y, x); // 递归调用
y -= (a / b) * x; // 使用之前的 x 更新 y
return d; // 返回 gcd
}
int main() {
int n;
cin >> n;
while (n--) {
int a, b, x, y;
cin >> a >> b;
exgcd(a, b, x, y); // 调用扩展欧几里得算法
cout << x << ' ' << y << endl; // 输出系数 x 和 y
}
return 0;
}
下面给出一个扩展欧几里得算法的应用。
2.Acwing 878.线性同余方程
实现思路:对于(a*x) mod m=b
,求解x。
- 恒等变形可以得
a*x=m*y'+b
=>a*x-m*y'=b
=>a*x+m*y=b
,此时就形似扩展欧几里得算法,则由裴蜀定理可知a*x+m*y=gcd(a,m)
必然有解,假如b是gcd(a,m)的倍数,即必然存在这样的x和y,使所求等式有解,只需等式两边扩大相应的倍数即可。此时x=x*(b/gcd(a,m))
- 这里最后**
x*(b/gcd(a,m))%m
**是为了得到最小的解,因为x的通解有无数个,防止超出int范围,就取一个最小的
具体实现代码(详解版):
#include <iostream>
using namespace std;
typedef long long LL; // 定义 LL 为 long long 类型
// 扩展欧几里得算法函数
int exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1; // 基本情况:如果 b == 0,x 为 1
y = 0; // y 为 0,因为 a * 1 + b * 0 = a
return a; // 返回 gcd(a, b)
}
int d = exgcd(b, a % b, y, x); // 递归调用
y -= (a / b) * x; // 使用之前的 x 更新 y
return d; // 返回 gcd
}
int main() {
int n;
cin >> n;
while (n--) {
int a, b, m, x, y;
cin >> a >> b >> m;
int d = exgcd(a, m, x, y); // 调用扩展欧几里得算法
if (b % d == 0) { // 如果 b 是 d 的倍数
cout << (LL)x * (b / d) % m << endl; // 输出最小解
} else {
puts("impossible"); // 输出 "impossible"
}
}
return 0;
}
3.中国剩余定理
x 有 k 个两两互质的数 m 1 、 m 2 、 … 、 m k 给定线性同余方程组 x ≡ a 1 ( m o d m 1 ) 、 x ≡ a 2 ( m o d m 2 ) 、 … 、 x ≡ a k ( m o d m k ) 求 x 解法: 令 M = m 1 × m 2 × ⋯ × m k , M i = M m i ,用 M i − 1 表示 M i 模的逆 ( 求 M i − 1 : M i × M i − 1 ≡ 1 ( m o d m i ) ,实质就是用扩展欧几里得求一个特殊的线性同余方程 : a × x ≡ 1 ( m o d m ) ) 则 x = a 1 × M 1 × M 1 − 1 + a 2 × M 2 × M 2 − 1 + ⋯ + a k × M k × M k − 1 可以验证 x 为该线性同余方程组的解(略) x 有k个两两互质的数m_{1}、m_{2}、\dots 、m_{k}\\给定线性同余方程组\ x\equiv a_{1}(mod\ m_{1})、x\equiv a_{2}(mod\ m_{2})、… 、x\equiv a_{k}(mod\ m_{k})\\\\求x解法:\\令M=m_{1}\times m_{2}\times \dots \times m_{k},M_{i}=\frac{M}{m_{i}},用M_{i}^{-1}表示M_{i} 模的逆 \\(求M_{i}^{-1}:M_{i} \times M_{i}^{-1} \equiv 1 (mod \ m_{i}),实质就是用扩展欧几里得求一个特殊的线性同余方程:a \times x\equiv 1 (mod \ m))\\则x=a_{1}\times M_{1} \times M_{1}^{-1} + a_{2}\times M_{2} \times M_{2}^{-1} + \dots + a_{k}\times M_{k} \times M_{k}^{-1}\\可以验证x为该线性同余方程组的解(略) x有k个两两互质的数m1、m2、…、mk给定线性同余方程组 x≡a1(mod m1)、x≡a2(mod m2)、…、x≡ak(mod mk)求x解法:令M=m1×m2×⋯×mk,Mi=miM,用Mi−1表示Mi模的逆(求Mi−1:Mi×Mi−1≡1(mod mi),实质就是用扩展欧几里得求一个特殊的线性同余方程:a×x≡1(mod m))则x=a1×M1×M1−1+a2×M2×M2−1+⋯+ak×Mk×Mk−1可以验证x为该线性同余方程组的解(略)
Acwing 204.表达整数的奇怪方式
求解推导:
公式
1. 对于两个式子,考虑将其合并: x ≡ m 1 ( % a 1 ) x ≡ m 2 ( % a 2 ) 则有: x = k 1 ∗ a 1 + m 1 , x = k 2 ∗ a 2 + m 2 , 进一步联立上面两式: k 1 ∗ a 1 + m 1 = k 2 ∗ a 2 + m 2 = = > k 1 ∗ a 1 + k 2 ∗ ( − a 2 ) = m 2 − m 1 . . . ① 也就是我们要找到一个最小的 k 1 , k 2 ,使得等式成立 ( 因为要求 x 最小,而 a 和 m 都是正数 ) 2. 对 a 1 和 a 2 使用扩展欧几里得得到最小公约数 d = g c d ( a 1 , − a 2 ) 即 k 1 ∗ a 1 + k 2 ∗ a 2 = d = g c d ( a 1 , − a 2 ) . . . ② 此时对于①和②式就出现了之前求线性同余方程的情况,即 m 2 − m 1 是 d 的倍数时 ( d ∣ m 2 − m 1 ) 方程有解 ②式两边乘 m 2 − m 1 d 就转化为①式 即 k 1 ∗ a 1 ∗ m 2 − m 1 d + k 2 ∗ a 2 ∗ m 2 − m 1 d = m 2 − m 1 3. 有解的情况下得到一组解 : k 1 = k 1 ∗ m 2 − m 1 d , k 2 = k 2 ∗ m 2 − m 1 d , 注意这里 k 就表示特解 4. 找最小正整数解:模尽倍数后,剩下的余数就是最小正整数解 k 1 = k 1 % a b s ( a 2 d ) , k 2 = k 2 % a b s ( a 1 d ) 5. 找满足方程的所有解 将一组方程的解带回方程: k 1 ∗ a 1 + k 2 ∗ a 2 = c 等式左边加减 a 1 ∗ a 2 d , 方程右边不变,可得: k 1 ∗ a 1 + k 2 ∗ a 2 + a 1 ∗ a 2 d − a 2 ∗ a 1 d = ( k 1 + a 2 d ) ∗ a 1 + ( k 2 − a 1 d ) ∗ a 2 = c 于是得到所有解集: k 1 = k 1 + a 2 d ∗ k , k 2 = k 2 − a 1 d ∗ k 6. 再代入 k 1 , k 2 得新的 x 为: x = ( k 1 + a 2 d ∗ k ) ∗ a 1 + m 1 = k 1 ∗ a 1 + m 1 + a 1 ∗ a 2 d ∗ k 令新的 a 1 ′ = a 1 ∗ a 2 d , 新的 m 1 ′ = k 1 ∗ a 1 + m 1 那么 x = a 1 ′ ∗ k + m 1 ′ , 这时就又回到了第一步,至此完成两个式子的合并 , 后续再来一个 a 3 , m 3 同理继续合并, n 个式子进行 n − 1 次合并得到最终式 1.对于两个式子,考虑将其合并:\\ x\equiv \ m_1 (\% a_1 )\\ x\equiv \ m_2 (\% a_2 )\\则有:x=k_1*a_1+m_1,x=k_2*a_2+m_2,\\进一步联立上面两式:k_1*a_1+m_1=k_2*a_2+m_2==>\mathbf {k_1*a_1+k_2*(-a_2)=m_2-m_1...①}\\也就是我们要找到一个最小的k_1,k_2,使得等式成立(因为要求x最小,而a和m都是正数)\\ \\2.对a_1和a_2使用扩展欧几里得得到最小公约数d=gcd(a_1,-a_2)\\即\mathbf {k_1*a_1+k_2*a_2=d=gcd(a_1,-a_2)...②}\\此时对于①和②式就出现了之前求线性同余方程的情况,即m_2-m_1是d的倍数时(d|m_2-m_1)方程有解\\②式两边乘\frac{m_2-m_1}{d}就转化为①式\\即\mathbf {k_1*a_1* \frac{m_2-m_1}{d} +k_2*a_2* \frac{m_2-m_1}{d}=m_2-m_1}\\ \\3.有解的情况下得到一组解:\\ \mathbf {k_1=k_1* \frac{m_2-m_1}{d} ,\ k_2=k_2* \frac{m_2-m_1}{d}},注意这里k就表示特解\\ \\4.找最小正整数解:模尽倍数后,剩下的余数就是最小正整数解\\k_1=k_1\ \% \ abs(\frac{a_2}{d}),k_2=k_2\ \% \ abs(\frac{a_1}{d})\\ \\5.找满足方程的所有解\\将一组方程的解带回方程:k_1*a_1+k_2*a_2=c\\等式左边加减\frac{a_1*a_2}{d},方程右边不变,可得:\\k_1*a_1+k_2*a_2+a_1*\frac{a_2}{d}-a_2*\frac{a_1}{d}=(k_1+\frac{a_2}{d})*a_1+(k_2-\frac{a_1}{d})*a_2=c\\ \mathbf {于是得到所有解集:k_1=k_1+\frac{a_2}{d}*k,k_2=k_2-\frac{a_1}{d}*k}\\ \\6.再代入k_1,k_2得新的x为:x=(k_1+\frac{a_2}{d}*k)*a_1+m_1\\=k_1*a_1+m_1+\frac{a_1*a_2}{d}*k\\令新的a_1^{'}=\frac{a_1*a_2}{d},新的m_1^{'}=k_1*a_1+m_1\\那么x=a_1^{'}*k+m_1^{'},这时就又回到了第一步,至此完成两个式子的合并,\\后续再来一个a_3,m_3同理继续合并,n个式子进行n-1次合并得到最终式 1.对于两个式子,考虑将其合并:x≡ m1(%a1)x≡ m2(%a2)则有:x=k1∗a1+m1,x=k2∗a2+m2,进一步联立上面两式:k1∗a1+m1=k2∗a2+m2==>k1∗a1+k2∗(−a2)=m2−m1...①也就是我们要找到一个最小的k1,k2,使得等式成立(因为要求x最小,而a和m都是正数)2.对a1和a2使用扩展欧几里得得到最小公约数d=gcd(a1,−a2)即k1∗a1+k2∗a2=d=gcd(a1,−a2)...②此时对于①和②式就出现了之前求线性同余方程的情况,即m2−m1是d的倍数时(d∣m2−m1)方程有解②式两边乘dm2−m1就转化为①式即k1∗a1∗dm2−m1+k2∗a2∗dm2−m1=m2−m13.有解的情况下得到一组解:k1=k1∗dm2−m1, k2=k2∗dm2−m1,注意这里k就表示特解4.找最小正整数解:模尽倍数后,剩下的余数就是最小正整数解k1=k1 % abs(da2),k2=k2 % abs(da1)5.找满足方程的所有解将一组方程的解带回方程:k1∗a1+k2∗a2=c等式左边加减da1∗a2,方程右边不变,可得:k1∗a1+k2∗a2+a1∗da2−a2∗da1=(k1+da2)∗a1+(k2−da1)∗a2=c于是得到所有解集:k1=k1+da2∗k,k2=k2−da1∗k6.再代入k1,k2得新的x为:x=(k1+da2∗k)∗a1+m1=k1∗a1+m1+da1∗a2∗k令新的a1′=da1∗a2,新的m1′=k1∗a1+m1那么x=a1′∗k+m1′,这时就又回到了第一步,至此完成两个式子的合并,后续再来一个a3,m3同理继续合并,n个式子进行n−1次合并得到最终式
具体实现代码(详解版):
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
// 扩展欧几里得算法函数
LL exgcd(LL a, LL b, LL &x, LL &y) {
if (!b) {
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x); // 递归调用
y -= a / b * x; // 更新 y
return d; // 返回 gcd(a, b)
}
int main() {
int n;
cin >> n; // 读取方程的数量
bool has_answer = true; // 假设有解
LL a1, m1;
cin >> a1 >> m1; // 读取第一个方程的系数 a1 和余数 m1
// 迭代处理剩余的方程
for (int i = 0; i < n - 1; i++) {
LL a2, m2;
cin >> a2 >> m2; // 读取下一个方程的系数 a2 和余数 m2
LL k1, k2;
LL d = exgcd(a1, a2, k1, k2); // 计算 a1 和 a2 的 gcd
// 如果 m2 - m1 不能被 gcd(a1, a2) 整除,则无解
if ((m2 - m1) % d) {
has_answer = false;
break;
}
// 更新 k1,找到合适的 k1
k1 *= (m2 - m1) / d;
LL t = a2 / d;
k1 = (k1 % t + t) % t; // 使 k1 是模 t 的正数解
// 更新 m1 和 a1
m1 = a1 * k1 + m1;
a1 = abs(a1 / d * a2); // 更新 a1 为最小公倍数
}
// 输出结果
if (has_answer) {
cout << (m1 % a1 + a1) % a1 << endl; // 输出最小非负解
} else {
puts("-1"); // 无解
}
return 0;
}
以上就是扩展欧几里得算法及其应用,关键是掌握欧几里得算法,应用可能推导较难,见机行事~