乘法逆元
定义
对于\(ax ≡ 1(mod p)\), 我们将使恒等式成立的最小正整数\(x\)称为\(a\)的逆元。
通过公式我们可以推出\(ax+py = 1(x,y为整数)\)所以根据裴蜀定理,\(gcd (a, p) ≡ 0 (mod 1)\), 即是\(gcd (a, p) = 1\)也就是\(a\)与\(p\)互质。
用处
\((a/b)\%q=(a*(1/b))\%q=(a*b^{-1})\%q\)
从这个等式中, 我们可以看出, 我们求\((a/b)\%q\)其实可以求\(a\)乘以\(b\)的逆元去模\(p\), 所以逆元的作用就是可以在模p的意义下代替除法。
求法
费马小定理
这个方法是最常用的, 根据费马小定理:
若\(a, p\)互质,且\(p\)为质数,则\(a^{p - 1}≡1(mod p)\)
根据这个公式, 两边同除\(a\)得
\(a^{p - 2}≡a^{-1}(mod p)\)
所以我们可以用\((a^{p-2}\%p + p)\%p\)求出逆元\((其中加上p是为了防止为负的情况)\)
代码:
long long fast_mul (long long a, long long b, long long p) {//快速乘, 防止long long的积溢出
long long ans = 0;
a %= p;
while (b) {
if (b & 1) {
ans += a;
ans %= p;
}
a = a << 1;
a %= p;
b >>= 1;
}
return ans;
}
long long fast_pow (long long a, long long b, long long p) {
long long ans = 1;
a %= p;
while (b) {
if (b & 1) {
ans = fast_mul (ans, a, p);
ans %= p;
}
a = fast_mul (a, a, p);
a %= p;
b >>= 1;
}
return ans;
}
long long inv (long long a, long long p) {//求逆元
return (fast_pow (a, p - 2, p) + p) % p;
}
拓展欧几里得
逆元的定义:
\(ax ≡ 1(mod p)\)
\(=>ax-py=1\)
然后我们就可以用拓展欧几里得求出\(x\)的特解, 然后再通过这个特解求出\(x\)的最小正整数解。
代码:
void ex_gcd (long long a, long long b, long long &x, long long &y) {
if (b == 0) {
x = 1;
y = 0;
return ;
}
ex_gcd (b, a % b, x, y);
long long r = x;
x = y;
y = r - a / b * y;
}
long long inv (long long a, long long p) {
long long x, y;
ex_gcd (a, p, x, y);
return (x + p) % p;
}
递推与递归
我们设\(p%i\)为r, \(\lfloor\frac{p}{i}\rfloor\)为\(k\):
则\(p = i * k + r\)
又因为\(p ≡ 0(mod p)\)
所以\(i * k + r ≡ 0(mod p)\)
两边同时乘以\(i^{-1}*r^{-1}\)得
\(i * k * i^{-1}*r^{-1} + r * i^{-1} * r^{-1} ≡ 0(mod p)\)
因为\(i*i^{-1} ≡ 1(mod p),r*r^{-1} ≡ 1(mod p)\)
所以\(k * r^{-1} + i^{-1} ≡ 0(mod p)\)
将\(k * r^{-1}\)移到右边得
\(i^{-1} ≡ -k * r^{-1}(mod p)\)
\(=>i^{-1} ≡ p - k * r^{-1}(mod p)\)
最终得到\(i^{-1} = (p - k * r^{-1} \% p) \% p\)
代码:
long long inv (long long a, long long p) {
if (a == 0 || a == 1) {
return 1;
}
return (p - (p / a) * inv (p % a, p) % p) % p;
}
不过如果这个式子最大的用处还是两处:
1.递归时可以用记忆化进行优化
2.可以在线性时间内用递推求出\([1, n]\)中所有数在模p意义下的逆元
关于第二个, 我们来分析一下。
首先肯定\(p \% a < a\)
那么每一个的逆元又只跟\(p % a\)的逆元有关
所以这个关系式是线性的
代码:
void inv (long long n, long long p) {
s[1] = 1;
s[0] = 1;
for (int i = 2; i <= n; i ++) {
s[i] = (p - ((p / i) * s[p % i]) % p) % p;
}
}