定义
非零实数 a∈R 的乘法逆元即为它的倒数 a−1 ,类似地,在模 p 意义下,我们可以定义 a 的乘法逆元 a−1modm ,即为模逆元。
标准定义:
对于非零整数a,m,如果存在b使得ab≡1(modm),则称b是a在模m意义下的逆元
这相当于 b 是线性同余方程 ax≡1(modm) 的解,则当且仅当 gcd(a,m)=1 时,方程有解即 a 的逆元存在。
求解方法
费马小定理快速幂法
这一方法主要适用于 m 为素数的情形。
首先,有费马小定理
ap−1≡1(modp)
其中 p 为素数,且 gcd(a,p)=1
我们要对线性同余方程
ax≡1(modp)
求解,可以发现存在解为
ap−1≡ax(modp)
则此时
x=ap−2modp
x 即为 a 在模 p 意义下的逆元,使用快速幂求解。时间复杂度为 O(logp)
// Binary exponentiation.
int pow(int a, int b, int m) {
long long res = 1, po = a;
for (; b; b >>= 1) {
if (b & 1) res = res * po % m;
po = po * po % m;
}
return res;
}
// Returns the modular inverse of a prime modulo p.
int inverse(int a, int p) { return pow(a, p - 2, p); }
扩展欧几里得算法
此方法适用于任意正整数 m 满足 gcd(a,m)=1
可知求解线性同余方程
ax≡1(modm)
即等价于求解二元一次不定方程
ax+my=1
其中 x 即为所求逆元,此时可用扩展欧几里得算法求解。时间复杂度为 O(logmin{a,m})
// Extended Euclidean algorithm.
void ex_gcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1;
y = 0;
} else {
ex_gcd(b, a % b, y, x);
y -= a / b * x;
}
}
// Returns the modular inverse of a modulo m.
// Assumes that gcd(a, m) = 1, so the inverse exists.
int inverse(int a, int m) {
int x, y;
ex_gcd(a, m, x, y);
return (x % m + m) % m;
}
线性递推
此方法适用于对前 n 个整数进行批量求逆元。
我们设 invi 为 i 在模 p 意义下的逆元。
首先,对于 n=1 ,有逆元 inv1=1
从而对于任意正整数 1<i≤n ,考虑带余除法。
p=qi+r(0≤r<i)
其中 q=⌊ip⌋,r=pmodi
两边对 p 取模,得到
0≡qi+rmodp(modp)
两边同时乘以 invi⋅invr 得
0≡q⋅invr+invi(modp)
从而
invi≡−q⋅invr(modp)
又 r<i ,则显然对任意正整数 i 均可由该地推关系计算得到逆元。
总体时间复杂度为 O(n)
前缀积逆推
设有序列 a1,a2,…,an ,要求出它们的逆元。若直接使用扩欧或者快速幂计算逆元,则时间复杂度为 O(nlogp) ,现在给出 O(n) 的方法。
首先计算前缀积 pren=a1a2…an(modp) ,然后计算 prefixn 的逆元 invpren
随后观察逆推关系,显然我们有:
prek=prek−1⋅ak
则:
ak=prek−1prek
又可知在模 p 意义下, prek−1prek=prek⋅invprek−1
两边同时求逆元得:
invak=invprek⋅prek−1
则我们只需要求出所有的 invprei ,如何求解呢?同样是使用递推关系来求解。
同样的,有:
prek=prek−1⋅ak
两边同时求逆元得:
invprek=invprek−1⋅invak
invprek−1=invakinvprek
同理,模 p 意义下,invakinvprek=invprek⋅ak
综上,有:
invprek−1=invprek⋅ak
通过这个方法,可以做到逆推求解每个 ai 的逆元。