取模意义下的逆元
如果一个线性同余方程 \(ax \equiv 1 \pmod b\) ,则 \(x\) 称为 \(a \pmod b\) 的逆元,记作 \(a^{-1}\)。
注意溢出
逆元相关的算法涉及大量的指数和乘法运算,一定要开 long long
。
费马小定理求逆元
费马小定理:若 \(p\) 为质数,则
$$
a^p \equiv a \pmod p
$$
由此我们可以得到,在 \(p\nmid a\) 时,有
\[
a^{p-2}\times a \equiv 1 \pmod p
\]
根据定义,\(a^{p-2}\) 正是 \(a\) 的一个逆元。我们可以使用快速幂以 \(O(\log_2{p})\) 的时间复杂度求出。
exgcd
求逆元
逆元的定义是一个线性同余方程,我们可以将它转化为一个二元一次不定方程:
\[
ax-py=1
\]
这种方程可以使用扩展欧几里得算法求出一组解 \((x,y)\),其中的 \(x\) 就是 \(a\) 的一个逆元。
注意,扩欧的 \(O(\log a)\) 时间复杂度是其理论上界。经实测,其带大约 \(\frac{1}{2}\) 的小常数。
使用条件
快速幂法使用了费马小定理,要求 \(p\) 是一个素数;而扩展欧几里得法只要求 \(\gcd(a, p) = 1\) 。
线性求逆元
例题
给定一个正整数 \(n\)(\(n\le 3\times 10^6\))和一个质数 \(p\)(\(n<p\)),输出所有 \(i\in[1,n]\) 的逆元 \(i^{-1}\)。
我们必须在 \(O(n)\) 的时间复杂度内求出总共 \(n\) 个数的逆元。对于数字 \(i\),假如我们已经求出了所有 \(j<i\) 的逆元 \(j^{-1}\)。将质数 \(p\) 对 \(i\) 取模:
\[
p=\lfloor\frac{p}{i}\rfloor\times i+r
\]
其中 \(r<i\);由此得到:
\[
\begin{align*}
\lfloor\frac{p}{i}\rfloor\times i+r\equiv 0 \pmod p\\
\lfloor\frac{p}{i}\rfloor\times i\equiv -r \pmod p
\end{align*}
\]
我们将方程两边同时乘以 \(i^{-1}r^{-1}\):
\[
i^{-1}\equiv -\lfloor\frac{p}{i}\rfloor\times r^{-1} \pmod p
\]
就得到了 \(i\) 的逆元。
代码
| int n, p;
int inv[N];
int main() {
cin >> n >> p;
inv[1] = 1;
for(int i = 2; i <= n; i++) {
inv[i] = ((long long)-(p / i) * inv[p % i]) % p + p;
}
return 0;
}
|
注意 int
溢出
当模数较大时(\(10^6\)),乘法会溢出 int
,因此一定要转换为 long long
。
通过此种方法,我们可以 \(O(n)\) 预处理,\(O(1)\) 求出组合数 \(\binom{a}{b}\),\(a,b\le n\)。
质因子分离法 / 模数不是质数
这种方法能实现对一个数字进行累乘、累除且模数 \(p\) 不是质数的情况。
我们先对 \(p\) 进行质因子分解;每次操作都将操作数 \(x\) 中所有 \(p\) 的质因子单独分离出来,单独处理;剩下的数一定和 \(p\) 互质,可以使用 exgcd
求出逆元。
代码
| int notPri[P], pri[P], pcnt;
// 筛出 [1,n] 区间内的所有质数
void prime() {
for(int i = 2; i <= p; i++) {
if(!notPri[i]) pri[++pcnt] = i;
for(int j = 1; j <= pcnt; j++) {
if(i * pri[j] > p) break;
notPri[i * pri[j]] = 1;
if(i % pri[j] == 0) break;
}
}
}
// 快速幂
int qpow(int a, int b) {
int res = 1;
while(b) {
if(b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
// 扩欧求逆元
void exgcd(const int &a, const int &b, int &x, int &y, int &d) {
if(b == 0) {
x = 1, y = 0;
d = a;
return;
}
exgcd(b, a % b, y, x, d);
y -= a / b * x;
}
// 互质逆元
int inv(int a) {
int x, y, d;
exgcd(a, p, x, y, d);
if(d != 1) throw "gcd(a, p) != 1";
x = (x % p + p) % p;
return x;
}
// pp[i] 储存 p 的质因数分解结果
// cnt[i] 表示质因子 pri[pp[i]] 的幂次
// num 表示跟 p 互质的那部分是多少
int pp[50], ppcnt;
int cnt[50], num = 1;
// 乘法
void mul(int x) {
for(int i = 1; i <= ppcnt; i++) {
while(x % pri[pp[i]] == 0) {
x /= pri[pp[i]];
cnt[i]++;
}
}
num = num * x % p;
}
// 除法
void div(int x) {
for(int i = 1; i <= ppcnt; i++) {
while(x % pri[pp[i]] == 0) {
x /= pri[pp[i]];
cnt[i]--;
}
}
num = num * inv(x) % p;
}
// 返回结果
int calc() {
int res = 1;
for(int i = 1; i <= ppcnt; i++) {
res = res * qpow(pri[pp[i]], cnt[i]) % p;
}
res = res * num % p;
return res;
}
|