
在算法竞赛的模运算场景中,“除法取模” 始终是令人头疼的难题 —— 同余式不满足除法封闭性,直接计算(a÷b)modp会导致结果错误。而乘法逆元正是破解这一困境的 “密钥”,它能将除法转化为乘法,让模运算中的除法操作合法可行。本文将从逆元的定义与核心作用出发,详解费马小定理、扩展欧几里得算法、线性递推三种主流求逆元方法,手把手教你掌握从单逆元求解到批量预处理的全流程,让你在模运算中彻底摆脱除法困扰。下面就让我们正式开始吧!
在算法竞赛的模运算场景中,“除法取模” 始终是令人头疼的难题 —— 同余式不满足除法封闭性,直接计算(a÷b)modp会导致结果错误。而乘法逆元正是破解这一困境的 “密钥”,它能将除法转化为乘法,让模运算中的除法操作合法可行。本文将从逆元的定义与核心作用出发,详解费马小定理、扩展欧几里得算法、线性递推三种主流求逆元方法,手把手教你掌握从单逆元求解到批量预处理的全流程,让你在模运算中彻底摆脱除法困扰。下面就让我们正式开始吧!
若整数a与模数m互质(即gcd(a,m)=1),且存在整数x使得a × x ≡ 1(mod m),则称x是a模m的乘法逆元,记作a−1(注意此处不是倒数,而是模意义下的逆元)。
举个直观例子:3×7=21≡1(mod10),因此 7 是 3 模 10 的乘法逆元,反过来 3 也是 7 模 10 的乘法逆元。
逆元的最大价值在于将模运算中的除法转化为乘法。对于算式(b÷a)mod p,由于直接除法不合法,我们可以用a的逆元a−1替代除法,转化为(b×a−1)mod p,结果与原算式等价。
计算(25÷5)mod3:
在算法竞赛中,很多题目要求结果对大质数(如1e9+7)取模,而计算过程中常涉及组合数、分式求和等含除法的场景。如果直接计算除法再取模,会因精度丢失或同余性质破坏导致错误,逆元则完美解决了这一问题。
乘法逆元存在的充要条件是:a与m互质(gcd(a,m)=1)。若a与m不互质,则不存在逆元。
求 2 模 4 的逆元:gcd(2,4)=2≠1,不存在整数x使得2x≡1(mod 4)(2x 的结果只能是 0、2 mod 4),因此逆元不存在。
费马小定理指出:若p为质数,且a与p互质,则

。
对定理变形可得:

。根据逆元定义,

就是a模p的乘法逆元。
快速幂算法能在O(logb)时间内计算

,是实现费马小定理求逆元的关键。
#include <iostream>
using namespace std;
typedef long long LL;
// 快速幂计算 (a^b) mod p
LL qpow(LL a, LL b, LL p) {
LL ret = 1;
a = a % p; // 底数取模,避免溢出
while (b > 0) {
if (b & 1) { // 二进制位为1时,结果乘当前底数
ret = ret * a % p;
}
a = a * a % p; // 底数平方
b >>= 1; // 指数右移一位
}
return ret;
}
// 费马小定理求逆元:p必须是质数,且a与p互质
LL mod_inv_fermat(LL a, LL p) {
return qpow(a, p - 2, p);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
LL a = 5, p = 1e9 + 7; // p是质数
LL inv = mod_inv_fermat(a, p);
cout << a << "模" << p << "的逆元为:" << inv << endl;
// 验证:5 * 200000001 mod 1e9+7 = 1(200000001是5模1e9+7的逆元)
return 0;
}扩展欧几里得算法(exgcd)的核心功能是求解二元一次不定方程ax+by=gcd(a,b)。当a与m互质时,gcd(a,m)=1,方程转化为ax+my=1。对该方程取模m,可得ax≡1(mod m),此时x的最小正整数解就是a模m的逆元。
#include <iostream>
using namespace std;
typedef long long LL;
// 扩展欧几里得算法:返回gcd(a,b),并通过引用返回方程ax+by=gcd(a,b)的特解(x,y)
LL exgcd(LL a, LL b, LL& x, LL& y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
LL x1, y1, d;
d = exgcd(b, a % b, x1, y1);
// 推导当前层特解
x = y1;
y = x1 - (a / b) * y1;
return d;
}
// 扩展欧几里得算法求逆元:a与m互质时返回逆元,否则返回-1
LL mod_inv_exgcd(LL a, LL m) {
LL x, y;
LL d = exgcd(a, m, x, y);
if (d != 1) {
return -1; // a与m不互质,无逆元
}
// 转化为最小正整数解
return (x % m + m) % m;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 示例1:模数为质数
LL a1 = 5, m1 = 1e9 + 7;
LL inv1 = mod_inv_exgcd(a1, m1);
cout << a1 << "模" << m1 << "的逆元:" << inv1 << endl; // 输出200000001
// 示例2:模数为合数(a与m互质)
LL a2 = 3, m2 = 10;
LL inv2 = mod_inv_exgcd(a2, m2);
cout << a2 << "模" << m2 << "的逆元:" << inv2 << endl; // 输出7
// 示例3:a与m不互质,无逆元
LL a3 = 2, m3 = 4;
LL inv3 = mod_inv_exgcd(a3, m3);
cout << a3 << "模" << m3 << "的逆元:" << inv3 << endl; // 输出-1
return 0;
}当需要求解1∼n中所有数模p的逆元时,线性递推法能在O(n)时间内完成预处理,效率远超逐个求解。
核心递推公式(p为质数):

设p=q×i+r(0<r<i),则r=p mod i。对等式取模p得:q×i+r ≡ 0(mod p),两边乘inv[i]×inv[r]:q×inv[r]+inv[i] ≡ 0(mod p),整理得:inv[i] ≡ −q×inv[r](mod p);由于q=p/i,代入得:inv[i]=(p−p/i)∗inv[p mod i] mod p(加p是为了保证结果为正)
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXN = 3e6 + 10; // 根据需求调整最大范围
LL inv[MAXN];
// 线性递推预处理1~n模p的逆元(p为质数)
void pre_inv(LL n, LL p) {
inv[1] = 1; // 1的逆元是1
for (int i = 2; i <= n; ++i) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
LL n = 10, p = 13; // p是质数
pre_inv(n, p);
cout << "1~" << n << "模" << p << "的逆元:" << endl;
for (int i = 1; i <= n; ++i) {
cout << i << "的逆元:" << inv[i] << endl;
}
/* 输出结果:
1的逆元:1
2的逆元:7
3的逆元:9
4的逆元:10
5的逆元:8
6的逆元:11
7的逆元:2
8的逆元:5
9的逆元:3
10的逆元:4
*/
return 0;
}方法 | 适用条件 | 时间复杂度 | 核心优势 | 局限性 |
|---|---|---|---|---|
费马小定理 + 快速幂 | 模数 p 为质数,a 与 p 互质 | O(logp) | 代码简洁,实现简单,单逆元高效 | 仅适用于质数模数 |
扩展欧几里得算法 | a 与 m 互质(m 可为任意整数) | O(logm) | 通用型强,支持合数模数 | 单逆元求解,批量效率低 |
线性递推法 | 模数 p 为质数,批量求 1~n 逆元 | O(n) | 批量处理速度极快,空间开销小 | 仅适用于质数模数和批量场景 |
题目链接:https://www.luogu.com.cn/problem/P3811

题目描述:给定n和p,求1∼n中所有整数在模p意义下的乘法逆元(p为质数)。
输入描述:一行两个正整数n和p。
输出描述:输出n行,第i行表示i在模p下的逆元。
示例输入:10 13 → 输出与线性递推示例一致。
核心思路:使用线性递推法,O(n)时间预处理所有逆元,效率最高。
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXN = 3e6 + 10;
LL inv[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
LL n, p;
scanf("%lld%lld", &n, &p);
inv[1] = 1;
for (int i = 2; i <= n; ++i) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
for (int i = 1; i <= n; ++i) {
printf("%lld\n", inv[i]);
}
return 0;
}scanf和printf替代cin和cout,提升大数据量下的读写速度;inv数组占用约 12MB(LL 类型),完全符合竞赛内存限制;题目链接:https://ac.nowcoder.com/acm/problem/226824

题目描述:求x模p意义下的逆元,若不存在则输出−1。
输入描述:第一行一个整数T,每组数据一行两个整数x和p(2≤x<p≤1e9)。
输出描述:每组数据输出逆元或−1。
示例输入:24 82 1000000007
示例输出:-1500000004
核心思路:
#include <iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL& x, LL& y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
LL x1, y1, d;
d = exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return d;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
LL x, p;
cin >> x >> p;
LL x0, y0, d;
d = exgcd(x, p, x0, y0);
if (d != 1) {
cout << -1 << endl;
} else {
// 转化为最小正整数解
cout << (x0 % p + p) % p << endl;
}
}
return 0;
}乘法逆元是模运算中处理除法的核心工具,三种求解方法各有适用场景,关键在于根据模数类型(质数 / 合数)和需求(单逆元 / 批量逆元)选择合适的方法。 如果在学习过程中遇到具体题目无法解决,或想了解逆元在更复杂场景(如扩展中国剩余定理)中的应用,可以随时留言交流。后续将持续更新数论进阶内容,敬请关注!