逆元(数论倒数)定义:如果ax≡1 (mod p),且gcd(a,p)=1(a与p互质,不互质的话x肯定不存在),则称a关于模p的乘法逆元为x,记为a^-1。(逆元不唯一)
逆元的作用:a/b ≡ a * inv(b) (mod p)
证明:a/b ≡ (a/b) * 1 ≡ (a/b) * (b * inv(b)) (模运算对加减乘可交换,对除不可交换)≡ a * inv(b) (mod p)
提到逆元就不得不提及取模运算和同余式的相关性质:
- 取模运算只能是整数,比如要求(3/4)% p应理解为3 * inv(4) % p,另外(a ^ b) % p != a ^ (b % p) 。对于负数取模的情况。在c++中,设a%b,如果b为负数,算出的结果与b为正数算出的结果一致。如果a为负数,则先按a为正数来算,最后再加一个负号。如果a,b都为负数,按照上述两条原则来计算即可。
- 同余式两边可以同时乘以一个数,可以同时加或者减一个数。对于除法而言,如果除数与模数互质的话,可以两边除以一个数,如果不互质的话,可能等号仍然成立也可能不再成立。
求逆元的三种方法:
- mod为质数,可以使用费马小定理来求,mod不为质数,可以使用欧拉定理来求
- 扩展欧几里得来求
- mod为质数时,可以线性筛出1-n的逆元
第一种方法:欧拉定理
费马小定理:
假如a是一个整数,p是一个质数,那么a^p - a 是p的倍数,a^p ≡a (mod p)
如果a不是p的倍数,这个定理也可以写成a^(p-1) ≡1 (mod p)。
欧拉定理:
对于任何两个互质的正整数x, m(m>2)有 : x^φ(m) ≡ 1(mod m)。
(证明略)
于是有x * x ^ (φ(m) - 1) ≡ 1 (mod m),于是inv(x) = x ^ (φ(m) - 1),特别的,当m为质数时,φ(m) = m-1,此时inv(x) = x ^ (m-2)。可以使用快速幂来求。复杂度为O(log mod)
int power(int a, int b, int p) { //快速幂计算(a^b) % p
int ans = 1 % p;
for(; b; b >>= 1) {
if (b & 1) ans = (ll)ans * a % p;
a = (ll)a * a % p;
}
return ans;
}
int phi(int n) { //求n的欧拉函数
int ans = n;
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) ans = ans / i * (i - 1);
while (n % i == 0) n /= i;
}
if (n > 1) ans = ans / n * (n - 1);
return ans;
}
第二种方法:扩展欧几里得
要求ax ≡ 1 (mod b),即找一个x使得ax + by = 1,由扩展欧几里得即可求得,扩展欧几里得可能返回负值,所以要加b模b。该种方法效率最高,复杂度为min(O(loga, logb)),当模数很大,但是要求逆元的数很小的时候非常适用。
代码:
void exgcd(int a, int b, int& x, int& y) { //求x, y使得ax + by = gcd(a, b)
if (b == 0) {
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= a / b * x;
}
int inverse(int a, int p) { //求a关于p的逆元
int ans, tmp;
exgcd(a, p, ans, tmp);
return (ans + p) % p;
}
第三种方法:线性筛
这种方法只能求1-mod的逆元,大于mod的无法求,逆元不存在时返回0。
代码:
void inverse_linear_sieve(int n, int p) { //线性求出1-n关于p的逆元
inv[1] = 1;
for (int i = 2; i <= n; i++) {
inv[i] = (p - p / i) * (ll)inv[p % i] % p;
}
}