扩展欧几里得算法
一、课上练习
编程练习
- 有理数取余:L49965
二、知识总结
✨ 核心思想
扩展欧几里得算法(Extended GCD)在求 gcd(a, b) 的同时,找到满足 ax + by = gcd(a, b) 的一组整数解 (x, y)。它是欧几里得算法的自然延伸,也是求模逆元、解线性同余方程的核心工具。
✨ 算法原理
欧几里得算法回顾:gcd(a, b) = gcd(b, a mod b),递归到 b = 0 时返回 a。
扩展思路:在递归过程中"回溯"出 x, y 的值。
设当前要求 ax + by = gcd(a, b),递归调用已求出:
b × x₁ + (a mod b) × y₁ = gcd(b, a mod b) = gcd(a, b)
因为 a mod b = a - ⌊a/b⌋ × b,代入得:
b × x₁ + (a - ⌊a/b⌋ × b) × y₁ = gcd(a, b)
整理得:
a × y₁ + b × (x₁ - ⌊a/b⌋ × y₁) = gcd(a, b)
因此:x = y₁, y = x₁ - ⌊a/b⌋ × y₁
递归基:当 b = 0 时,gcd(a, 0) = a,此时 x = 1, y = 0(因为 a×1 + 0×0 = a)。
✨ 代码实现
1#include <iostream>
2using namespace std;
3typedef long long ll;
4
5// 返回 gcd(a, b),同时通过引用返回 x, y
6// 使得 ax + by = gcd(a, b)
7ll exgcd(ll a, ll b, ll &x, ll &y) {
8 if (b == 0) {
9 x = 1;
10 y = 0;
11 return a;
12 }
13 ll d = exgcd(b, a % b, y, x);
14 // 注意这里参数顺序:递归时交换了 x, y 的位置
15 // 递归返回后 x 对应 y₁,y 对应 x₁
16 y -= (a / b) * x;
17 return d;
18}
19
20int main() {
21 ll a, b;
22 cin >> a >> b;
23 ll x, y;
24 ll d = exgcd(a, b, x, y);
25 cout << "gcd(" << a << ", " << b << ") = " << d << endl;
26 cout << a << " * " << x << " + " << b << " * " << y
27 << " = " << a * x + b * y << endl;
28 return 0;
29}1#include <iostream>
2using namespace std;
3typedef long long ll;
4
5ll exgcd_iter(ll a, ll b, ll &x, ll &y) {
6 ll x0 = 1, y0 = 0; // a 的系数
7 ll x1 = 0, y1 = 1; // b 的系数
8 while (b != 0) {
9 ll q = a / b;
10 // 更新 a, b
11 ll temp = b;
12 b = a % b;
13 a = temp;
14 // 更新系数
15 temp = x1;
16 x1 = x0 - q * x1;
17 x0 = temp;
18
19 temp = y1;
20 y1 = y0 - q * y1;
21 y0 = temp;
22 }
23 x = x0;
24 y = y0;
25 return a;
26}
27
28int main() {
29 ll a, b;
30 cin >> a >> b;
31 ll x, y;
32 ll d = exgcd_iter(a, b, x, y);
33 cout << "gcd = " << d << endl;
34 cout << a << " * " << x << " + " << b << " * " << y
35 << " = " << d << endl;
36 return 0;
37}1#include <iostream>
2using namespace std;
3typedef long long ll;
4
5ll exgcd(ll a, ll b, ll &x, ll &y) {
6 if (b == 0) { x = 1; y = 0; return a; }
7 ll d = exgcd(b, a % b, y, x);
8 y -= (a / b) * x;
9 return d;
10}
11
12// 求 a 模 m 的逆元
13// 即求 x 使得 ax ≡ 1 (mod m)
14// 条件:gcd(a, m) = 1
15ll mod_inverse(ll a, ll m) {
16 ll x, y;
17 ll d = exgcd(a, m, x, y);
18 if (d != 1) return -1; // 逆元不存在
19 return (x % m + m) % m; // 保证返回正数
20}
21
22int main() {
23 ll a, m;
24 cin >> a >> m;
25 ll inv = mod_inverse(a, m);
26 if (inv == -1) {
27 cout << "逆元不存在(gcd != 1)" << endl;
28 } else {
29 cout << a << " 模 " << m << " 的逆元为 " << inv << endl;
30 cout << "验证: " << a << " * " << inv << " mod " << m
31 << " = " << (a * inv) % m << endl;
32 }
33 return 0;
34}1#include <iostream>
2using namespace std;
3typedef long long ll;
4
5ll exgcd(ll a, ll b, ll &x, ll &y) {
6 if (b == 0) { x = 1; y = 0; return a; }
7 ll d = exgcd(b, a % b, y, x);
8 y -= (a / b) * x;
9 return d;
10}
11
12// 解 ax ≡ c (mod m)
13// 等价于解 ax + my = c
14void solve_congruence(ll a, ll c, ll m) {
15 ll x, y;
16 ll d = exgcd(a, m, x, y);
17
18 if (c % d != 0) {
19 cout << "无解" << endl;
20 return;
21 }
22
23 // 一个特解
24 ll x0 = (ll)(((__int128)x * (c / d)) % (m / d));
25 x0 = (x0 % (m / d) + m / d) % (m / d);
26
27 cout << "共有 " << d << " 个模 " << m << " 下不同的解" << endl;
28 cout << "最小非负解: " << x0 << endl;
29 cout << "所有解 (mod " << m << "):" << endl;
30 for (ll i = 0; i < d; i++) {
31 cout << " x = " << (x0 + i * (m / d)) % m << endl;
32 }
33}
34
35int main() {
36 ll a, c, m;
37 cin >> a >> c >> m;
38 solve_congruence(a, c, m);
39 return 0;
40}✨ 应用示例
示例 1:求 3 模 7 的逆元
解 3x + 7y = 1,用扩展欧几里得:
exgcd(3, 7) → exgcd(7, 3) → exgcd(3, 1) → exgcd(1, 0)
回溯得 x = 5, y = -2。验证:3 × 5 + 7 × (-2) = 15 - 14 = 1
所以 3 模 7 的逆元是 5。验证:3 × 5 = 15 ≡ 1 (mod 7)
示例 2:解 6x ≡ 4 (mod 10)
gcd(6, 10) = 2, 4 % 2 = 0,有 2 个解。
先解 6x + 10y = 2:exgcd 得 x=2, y=-1(6×2+10×(-1)=2)
乘以 4/2=2:x₀ = 4
解的间隔:10/2 = 5
所有解 (mod 10):x = 4, x = 9
验证:6×4 = 24 ≡ 4 (mod 10), 6×9 = 54 ≡ 4 (mod 10)
✨ 解题步骤详解
求模逆元:
- 确认 gcd(a, m) = 1
- 调用 exgcd(a, m, x, y)
- 将 x 调整为正数:
(x % m + m) % m
解线性同余方程 ax ≡ c (mod m):
- 计算 d = gcd(a, m)
- 若 d 不能整除 c,无解
- 若 d 整除 c,方程等价于 (a/d)x ≡ (c/d) (mod m/d)
- 求 a/d 模 m/d 的逆元
- 得一个特解 x₀,通解为 x = x₀ + k(m/d),共 d 个不同解(模 m 下)
✨ 常见错误
- 递归版中参数传递时 x, y 的位置搞反
y -= (a/b) * x中的 a/b 应为整数除法(C++ 中对正数自动向下取整,负数需注意)- 求逆元后忘记处理负数结果
- 线性同余方程中,忘记先除以 gcd 再求解
- 混淆"模逆元存在的条件"(gcd=1)和"同余方程有解的条件"(gcd|c)
- 大数乘法溢出,需要
__int128或分步取模
✨ 算法评价
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| exgcd | O(log min(a,b)) | 与欧几里得算法相同 |
| 求模逆元 | O(log m) | 一次 exgcd 调用 |
| 解同余方程 | O(log m + d) | d 为解的个数 |
扩展欧几里得算法是竞赛数论的"瑞士军刀",几乎所有涉及同余运算的问题都会用到它。务必熟练掌握递归和迭代两种写法,以及它在求逆元和解同余方程中的应用。
三、课后练习
编程练习
- 青蛙的约会:P1516