乘法逆元
(1、)定义:(frac{1}{a} equiv bpmod{p}),则称(b)为(a)在模(p)意义下的乘法逆元,记为(inv(a))或(a^{-1})。
(2、)在模(p)意义下讨论乘法逆元的存在性
设(a^{-1} = x)
(acdot x equiv 1)
(Leftrightarrow a cdot x = 1 - py)
(Leftrightarrow a x+py = 1)
裴蜀定理告诉我们,该方程当且仅当(gcd(a,p) = 1)时有解。也就是说,当且仅当(a)与模数(p)互质时存在(a)的乘法逆元。因此大部分有理数取模问题给定的模数都是质数,这样能够在(amod p eq 0)的情况下保证存在(a)的逆元。
(3、)乘法逆元的四种推导方式
-
扩展欧几里得算法
求(a)的逆元,也就是求出(Leftrightarrow a x+py = 1)的一组解,使用扩展欧几里得算法直接求解得(x)即可。
void exgcd(int& x, int& y, int a, int b) { if (!p) { x = 1, y = 0; return; } exgcd(y, x, b, a % b); y -= a / b * x; }
-
费马小定理/扩展欧拉定理
费马小定理:对于质数(p),(a^{p} equiv a pmod{p})
即 (a^{p-1} equiv 1pmod {p})
也就是说,(a^{p-2})即为(a mod p)意义下的逆元,使用快速幂求解。
费马小定理是欧拉定理在(p)为质数时的特殊情况,实现起来没有区别。这里不深入展开叙述欧拉定理。
LL fpow(LL a, LL p) { LL b = p - 2, ret = 1; for (; b; b >>= 1) { if (b & 1) ret = ret * a % p; a = a * a % p; } return ret; }
-
线性递推(1sim n)的逆元
显然有(1^{-1} = 1).
设(r = p mod i, k = lfloor frac{p}{i} floor)
(Rightarrow ki + r equiv 0 pmod p)
两边同乘(i^{-1}r^{-1}),得
(kr^{-1} + i^{-1} equiv 0 pmod p)
(Leftrightarrow i^{-1} = -lfloor frac{p}{i} floor cdot (p mod i)^{-1} pmod p)
得到如上递推式,解毕。
void get_inv(int n, int p) { inv[1] = 1; for (int i = 2; i <= n; ++i) f[i] = (LL)(p - p / i) * inv[p%i] % p;//p - p / i:去掉负数 }
-
线性递推(a_1 sim a_n)的逆元((a_i in N_+))
以下表达式均在(mod p) 意义下。
设(f_i = prod_{j = 1}^{i}a_j)
首先可以线性递推求出(f_{1 sim n})。
我们发现,对((f_i)^{-1})可推导出如下递推式:
$ f_i imes (f_{i})^{-1} equiv 1$
(Rightarrow a_i cdot f_{i-1} cdot (f_i)^{-1} equiv 1)
即((f_i)^{-1} equiv (f_{i+1})^{-1} imes a_{i+1})
那么我们可以结合费马小定理/扩展欧几里得算法
求出((f_n)^{-1}),然后倒着线性迭代求出((f_{1 sim n-1})^{-1})。
现在我们求出了原数列前缀积数列的逆元,考虑着手由({f}^{-1})数组推导出({a}^{-1})。
(a_i imes a_i^{-1} equiv 1)
(Leftrightarrow frac{f_i}{f_{i-1}} cdot a_i^{-1} equiv 1)
(Leftrightarrow a_i^{-1} equiv frac{f_{i-1}}{f_i})
即(a_i^{-1} equiv f_{i-1} cdot (f_i)^{-1})。
得到如上递推式,解毕。
因此,最后我们再使用费马小定理求出(a_1^{-1}),就可以递推一遍求出原数列各项的逆元了。
int a[maxn], inv[maxn], V[maxn], f[maxn], n, k, p; LL ans; int fpow(LL a, LL b) { LL ret = 1; while (b) { if (b & 1) ret = ret * a % p; b >>= 1; a = a * a % p; } return ret; } void get_inv() { f[0] = 1; for (int i = 1; i <= n; ++i) { read(a[i]); f[i] = (LL)a[i] * f[i-1] % p; } V[n] = fpow(f[n], p - 2); for (int i = n - 1; i; --i) V[i] = (LL)V[i+1] * (a[i+1]) % p; for (int i = n; i > 1; --i) inv[i] = (LL)f[i-1] * V[i] % p; inv[1] = fpow(a[1], p - 2); }