乘法逆元是群论里非常重要的一个定义,这里不涉及群论,只探讨其定义和求法。
定义:
如果有ab ≡ 1(modp),则称b是mod p意义下a的乘法逆元。记b=inv(a) 或 b=a-1。
定义介绍完了,下面说说它的几种求法。
方法一:欧拉筛
第一想法很容易就能想到,对于每个合数 a ,我们把所有它的因子的逆元筛出来再相乘即可。(不要说枚举这种操作)但是,这个方法非常慢,所以我们要寻找更快的理论。
方法二:扩展欧几里得
对于ax ≡ 1(modp),可以转化为ax = kp + 1,移项就可以用exgcd求了,复杂度o(nlogn)。
#include <iostream>
long long exgcd(long long a, long long b, long long &x, long long &y)
{
if(a == 0 && b == 0)
return -1;
if(b == 0)
{
x = 1;
y = 0;
return a;
}
long long d = exgcd(b, a%b, y, x);
y -= a/b*x;
return d;
}
long long mod_reverse(long long a, long long n)
{
long long d = exgcd(a, n, x, y);
if(d == 1)
return (x%n+n) % n;
else
return -1;
}
方法三:费马小定理
费马小定理:当 p 为素数时, ap-1=1 (mod p) 。那么 a * ap-2 = 1 (mod p) 。快速幂求出 ap-2 即可,复杂度o(nlogn)。
方法四:欧拉定理
由aφ§ ≡ 1 (mod p) 得 aφ(p)−1是a的逆元,同样的用快速幂求。
其中,φ(x)=x * (1 - 1 / p1)(1 - 1 / p2)(1 - 1 / p3)(1 - 1 / p4) …… (1 - 1 / pn) ,其中p1,p2 … pn为 x 的所有质因数,x是正整数, φ(1)=1。注:每种质因数只有一个。
(https://blog.csdn.net/Lytning/article/details/24432651 这个是欧拉函数线性筛的博客)
方法五:线性递推
(从这个大佬的博客里找到的 https://blog.csdn.net/sdfzchy/article/details/76098066)
于是得到, inv[i] = (mod - mod / i) * inv[mod%i] % mod,时间复杂度o(n)。
void inv(LL mod)
{
inv[1] = 1;
for(int i = 2; i <= mod-1; i++)
{
inv[i] = (mod - mod / i) * inv[mod%i] % mod;
}
}
PS:
1、在求解a / b % m时,可以转化为(a % (b * m)) / b。
证明:令k = (a / b) / m(向下取整), x = (a / b) % m,
a / b = k * m + x (x < m),
a = k* b * m + b * x,
a % (b * m) = b * x,
a % (b * m) / b = x,
a / b % m = a % (b * m) / b。
证毕。
(公式适用于很多情况:m不必是素数,b和m也不必互质)
上面的公式适用于b较小,a需要在线求且较大的时候。
2、求组合数的时候可以用逆元打表求,结合递推和费马小定理。
fac[0] = 1;
for(int i = 1; i <= MAX; i++)
fac[i] = (fac[i - 1] * i) % MOD;
inv_fac[MAX] = qpow(fac[MAX], MOD - 2);
for(int i = MAX - 1; i >= 0; i--)
inv_fac[i] = (inv_fac[i + 1] * (i + 1)) % MOD;
差不多就是这些吧,还有新的东西还会补充。