【初等数论】乘法逆元略解
乘法逆元
\(1、\)定义:\(\frac{1}{a} \equiv b\pmod{p}\),则称\(b\)为\(a\)在模\(p\)意义下的乘法逆元,记为\(inv(a)\)或\(a^{-1}\)。
\(2、\)在模\(p\)意义下讨论乘法逆元的存在性
设\(a^{-1} = x\)
\(a\cdot x \equiv 1\)
\(\Leftrightarrow a \cdot x = 1 - py\)
\(\Leftrightarrow a x+py = 1\)
裴蜀定理告诉我们,该方程当且仅当\(gcd(a,p) = 1\)时有解。也就是说,当且仅当\(a\)与模数\(p\)互质时存在\(a\)的乘法逆元。因此大部分有理数取模问题给定的模数都是质数,这样能够在\(a\bmod p \neq 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 1\pmod {p}\)
也就是说,\(a^{p-2}\)即为\(a \bmod 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; }
-
线性递推\(1\sim n\)的逆元
显然有\(1^{-1} = 1\).
设\(r = p \bmod i, k = \lfloor \frac{p}{i} \rfloor\)
\(\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} \rfloor \cdot (p \bmod 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_+)\)
以下表达式均在\(\bmod p\) 意义下。
设\(f_i = \prod_{j = 1}^{i}a_j\)
首先可以线性递推求出\(f_{1 \sim n}\)。
我们发现,对\((f_i)^{-1}\)可推导出如下递推式:
$ f_i \times (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} \times a_{i+1}\)
那么我们可以结合费马小定理/扩展欧几里得算法
求出\((f_n)^{-1}\),然后倒着线性迭代求出\((f_{1 \sim n-1})^{-1}\)。
现在我们求出了原数列前缀积数列的逆元,考虑着手由\(\{f\}^{-1}\)数组推导出\(\{a\}^{-1}\)。
\(a_i \times 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); }