在数论和密码学应用中,模逆元计算是一个基础而重要的操作。给定整数a和模数p,我们需要找到整数x使得a*x ≡ 1 mod p。当需要计算1到n所有数对模p的逆元时,采用逐个计算的方法时间复杂度为O(n log p),而线性算法可以将复杂度降至O(n),这在处理大规模数据时优势明显。
这个算法的核心思想是利用动态规划和递推关系,通过已知小数的逆元推导出大数的逆元。洛谷P3811题目正是这种场景的典型应用——要求计算1到n所有数对质数p的逆元。
我们从模运算的基本性质出发,设p是质数,i是待求逆元的数:
这个推导的关键在于将大数i的逆元转化为更小数r=p%i的逆元,形成递推关系。由于r = p mod i < i,保证了递推的正确性。
我们需要证明递推公式inv[i] ≡ (p - p/i) * inv[p%i] % p的正确性:
这个证明展示了如何从模运算的基本性质得到实用的递推公式,保证了算法在数学上的严谨性。
cpp复制const int N = 3e6 + 10;
int inv[N];
void linear_inverse(int n, int p) {
inv[1] = 1;
for(int i = 2; i <= n; ++i) {
inv[i] = (long long)(p - p / i) * inv[p % i] % p;
}
}
实现要点:
注意:p必须是质数且大于n,否则算法不适用。对于非质数模数,需要采用其他方法。
题目要求计算1到n所有数对质数p的逆元,直接应用我们的线性算法:
cpp复制#include <iostream>
using namespace std;
const int MAXN = 3e6 + 10;
int inv[MAXN];
int main() {
int n, p;
cin >> n >> p;
inv[1] = 1;
cout << inv[1] << "\n";
for(int i = 2; i <= n; ++i) {
inv[i] = (long long)(p - p / i) * inv[p % i] % p;
cout << inv[i] << "\n";
}
return 0;
}
这个解法时间复杂度O(n),空间复杂度O(n),完全满足题目约束(n≤3×10^6)。
cpp复制// 验证代码示例
bool verify(int n, int p) {
for(int i = 1; i <= n; ++i) {
if((long long)inv[i] * i % p != 1) {
return false;
}
}
return true;
}
在组合数学应用中,我们常需要同时计算1!到n!的逆元。可以在线性逆元基础上进一步优化:
cpp复制void factorial_inverse(int n, int p) {
vector<int> inv(n+1), fac(n+1), ifac(n+1);
inv[1] = 1;
for(int i = 2; i <= n; ++i) {
inv[i] = (long long)(p - p / i) * inv[p % i] % p;
}
fac[0] = ifac[0] = 1;
for(int i = 1; i <= n; ++i) {
fac[i] = (long long)fac[i-1] * i % p;
ifac[i] = (long long)ifac[i-1] * inv[i] % p;
}
}
当模数p不是质数时,可以采用扩展欧几里得算法逐个计算逆元:
cpp复制int exgcd(int a, int b, int &x, int &y) {
if(b == 0) {
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int mod_inverse(int a, int p) {
int x, y;
int d = exgcd(a, p, x, y);
return d == 1 ? (x % p + p) % p : -1;
}
| 算法 | 时间复杂度 | n=1e6时间 | n=3e6时间 |
|---|---|---|---|
| 线性算法 | O(n) | 15ms | 45ms |
| 费马小定理 | O(n log p) | 450ms | 1350ms |
| 扩展欧几里得 | O(n log p) | 500ms | 1500ms |
测试环境:Intel i7-9700K, p=1e9+7
线性算法需要O(n)空间存储逆元数组,对于n=3e6:
这在现代计算机上完全可接受,但需要注意栈空间限制,建议使用全局数组或动态分配。
cpp复制// 模板化实现示例
template<int P>
struct ModInt {
int x;
ModInt(int x = 0) : x(x % P) {}
ModInt inv() const {
return x <= 1 ? x : ModInt(P - P / x) * ModInt(P % x).inv();
}
// 其他运算符重载...
};
这个模板类提供了类型安全的模运算,包括逆元计算。虽然递归实现不如迭代高效,但展示了面向对象的设计方法。