扩展欧几里得求 ax+by=1 的特解,对 b 取模即得最小正整数解(模逆元模板题)。
OJ: roj
题目 ID: 3089
难度:普及+/提高-
标签:数学扩展欧几里得模逆元同余方程python
创建: 2026-10-01 16:14
更新: 2026-10-07 13:50
形式化题目
给定正整数
的最小正整数解
样例:
正解
思路
朴素做法为什么不行。 最直接的想法是枚举
第一步:把同余方程改写成不定方程。
反过来,这个方程的任何一组解
第二步:裴蜀定理告诉我们解长什么样。 方程
第三步:扩展欧几里得算法求出一组特解。 利用最大公约数的递归性质
- 递归到
时, ,此时 ,一组解就是 ; - 设子问题
已经解出 ,把 代入:
对比
以
flowchart TB
A["exgcd(3, 10)<br/>a//b = 0, a mod b = 3<br/>x = y′, y = x′ − 0·y′"]
B["exgcd(10, 3)<br/>a//b = 3, a mod b = 1<br/>x = y′, y = x′ − 3·y′"]
C["exgcd(3, 1)<br/>a//b = 3, a mod b = 0<br/>x = y′, y = x′ − 3·y′"]
D["exgcd(1, 0)<br/>b = 0 → 返回 (1, 0)"]
A --> B --> C --> D
D -. "返回 (x′, y′) = (1, 0)" .-> C
C -. "x = 0, y = 1 − 3·0 = 1<br/>返回 (0, 1)" .-> B
B -. "x = 1, y = 0 − 3·1 = −3<br/>返回 (1, −3)" .-> A
A -. "x = −3, y = 1 − 0·1 = 1<br/>返回 (−3, 1)" .-> Z["3×(−3) + 10×1 = 1 ✓"]
递归先一路下降到基例 exgcd(1, 0),再沿虚线逐层回代:每一层只做一次"系数换算"(
第四步:从特解到最小正整数解。 若
代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 16:14
# update_at: 2026-10-01 16:14
import sys
def exgcd(a: int, b: int) -> tuple[int, int]:
"""解 a*x + b*y = gcd(a, b),返回一组整数解 (x, y)。
递归到子问题 b*x' + (a mod b)*y' = gcd 后,用 a mod b = a - (a//b)*b
换元回代,即可由 (x', y') 拼出当前层的 (x, y)。
"""
if b == 0:
return 1, 0
x, y = exgcd(b, a % b)
return y, x - (a // b) * y
def solve() -> None:
a, b = map(int, sys.stdin.buffer.read().split())
x, _ = exgcd(a, b)
# 题目保证 gcd(a, b) = 1,故 a*x ≡ 1 (mod b);
# Python 取模结果落在 [0, b),而 x ≢ 0 (mod b),所以就是最小正整数解。
print(x % b)
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 18:11
* update_at: 2026-10-06 18:11
*/
#include <cstdio>
typedef long long ll;
ll a, b; // 题目给出的两个正整数,2 <= a, b <= 2*10^9
ll gx, gy; // 保存 exgcd 求出的一组解:a*gx + b*gy = gcd(a, b)
// 扩展欧几里得:解 a*x + b*y = gcd(a, b),结果存入 gx, gy,返回 gcd
// 递归基例:b = 0 时 gcd = a,一组解是 (1, 0)
// 回代:设子问题 b*x' + (a%b)*y' = g 已解出,代入 a%b = a - (a/b)*b
// 整理得 a*y' + b*(x' - (a/b)*y') = g,即 x = y', y = x' - (a/b)*y'
ll exgcd(ll a, ll b) {
if (b == 0) {
gx = 1;
gy = 0;
return a;
}
ll g = exgcd(b, a % b);
ll x = gy;
ll y = gx - (a / b) * gy;
gx = x;
gy = y;
return g;
}
int main() {
scanf("%lld %lld", &a, &b);
exgcd(a, b);
// 方程 ax ≡ 1 (mod b) 等价于 ax + by = 1,保证有解即 gcd(a,b) = 1
// 解集是公差为 b 的等差数列,特解 gx 对 b 取模即最小正整数解
// 因为 b >= 2 且 gx*1 ≡ 1,gx mod b 不会等于 0,无需特判
ll ans = gx % b;
if (ans <= 0) ans += b; // C++ 取模可能得 0 或负数,兜底修正
printf("%lld\n", ans);
return 0;
}复杂度
- 时间复杂度:
。扩展欧几里得与欧几里得算法层数相同,最坏情况出现在相邻 Fibonacci 数上, 以内不超过约 45 层递归。 - 空间复杂度:
,即递归栈深度。 - Python 实现说明:全程只有整数加减乘除与取模,无大数膨胀,1 秒内轻松完成;递归深度约 45 层,远低于默认上限。
总结
- 同余方程
等价于不定方程 ;"保证有解"即 (裴蜀定理)。 - 扩展欧几里得在欧几里得算法的回代路上顺手解出
:基例 ,回代公式 , 。 - 解集是公差为
的等差数列,特解对 取模即最小正整数解,无需特判。 - 本题是"模逆元"的标准模板:
就是 在模 下的乘法逆元,后续组合数学、CRT 等题目都建立在它之上。