「学习笔记」逆元
定义
如果一个线性同余方程 \(ax \equiv 1 \pmod b\),则 \(x\) 称为 \(a \bmod b\) 的逆元,记作 \(a^{-1}\)。
如何求逆元
利用费马小定理
前置知识:费马小定理。
如果 \(b\) 是质数,那么 \(a^{b - 1} \equiv 1\pmod b\),即 \(a \times a^{b - 2} \equiv 1 \pmod b\),逆元 \(x = a^{b - 2}\)。
我们可以用快速幂来实现。
快速幂代码:
ll qpow(ll x, ll y, ll mod) {
ll res = 1;
while (y) {
if (y & 1) {
res = res * x % mod;
}
y >>= 1;
x = x * x % mod;
}
return res % mod;
}
利用扩展欧几里得算法
前置知识:扩展欧几里得算法。
利用扩展欧几里得算法求解,就是解不定方程 \(ax \equiv 1 \pmod b\)。
接下来按照扩展欧几里得算法解不定方程来做就行了。
\(ax + by \equiv c\) 的有解的要求是 \(c\) 是 \(\gcd(a, b)\) 的倍数,因此欧几里得算法求逆元的要求是 \(a \perp b\)。
扩展欧几里得算法的代码如下:
int exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
return a;
}
int g = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return g;
}
线性求逆元
\(1\) 的逆元为 \(1\),这是我们推算其他数的逆元的基础。
对于一个模数 \(p\),我们设 \(k = \left \lfloor \dfrac{p}{i} \right \rfloor, j = p \bmod i\),则 \(p = k \cdot i + j\),在 \(\pmod p\) 意义下,\(k \cdot i + j \equiv 0 \pmod p\),移项得 \(j \equiv -k \cdot i\),同余式两边同乘 \(j^{-1} \cdot i^{-1}\) 得 \(i^{-1} \equiv -k \cdot j^{-1}\),将 \(k = \left \lfloor \dfrac{p}{i} \right \rfloor, j = p \bmod i\) 得 \(i^{-1} \equiv - \left \lfloor \dfrac{p}{i} \right \rfloor \cdot (p \bmod i)^{-1}\),我们可以用 \(p - \left \lfloor \dfrac{p}{i} \right \rfloor\) 来代替 \(- \left \lfloor \dfrac{p}{i} \right \rfloor\) 这个负数。
最后的递推公式:\(i^{-1} \equiv \left ( p - \left \lfloor \dfrac{p}{i} \right \rfloor \right ) \cdot \left (p \bmod i \right )^{-1} \pmod p\)
代码:
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
const int N = 3e6 + 5;
int n, p;
ll inv[N];
int main() {
n = read<int>(), p = read<int>();
inv[1] = 1;
rep (i, 2, n, 1) {
inv[i] = 1ll * (p - (p / i)) * inv[p % i] % p;
}
rep (i, 1, n, 1) {
cout << inv[i] << '\n';
}
return 0;
}
线性求任意 n 个数的逆元
给定任意 \(n\) 个数,要求线性时间复杂度求出这 \(n\) 个数的逆元。
先预处理出前 \(n\) 个数的前缀积 \(pro\),随后处理出 \(pro_n\) 的逆元 \(inv_n\),即 \(inv_n = (s_1 \cdot s_2 \cdot s_3 \dots s_n) ^ {-1}\),\(s_i\) 的逆元 \(s_i^{-1} = inv_n \cdot pro_{i - 1} \cdot \prod_{j = i + 1}^{n} s_j\),这样就能线性求出任意 \(n\) 个数的逆元了。
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
rep (i, 1, n, 1) {
s[i] = read<ll>();
}
pro[0] = 1;
rep (i, 1, n, 1) {
pro[i] = pro[i - 1] * s[i] % mod;
}
inv[n] = qpow(pro[n], mod - 2);
per (i, n, 1, 1) {
inv[i - 1] = inv[i] * s[i] % mod;
}
rep (i, 2, n, 1) {
inv[i] = inv[i] * pro[i - 1] % mod;
}
线性求阶乘的逆元
阶乘计算公式:\(n! = \prod_{i = 1}^{n} i\),\(0! = 1\)
与上面“线性求任意 \(n\) 个数的逆元”一样的道理,先预处理出阶乘 \(fac\),随后我们算出 \(fac_n\) 的逆元 \(inv_n (inv_n = n!^{-1})\),然后可以通过倒叙枚举来求出每个阶乘的逆元。
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
fac[0] = 1;
rep (i, 1, n, 1) {
fac[i] = fac[i - 1] * i % mod;
}
inv[n] = qpow(fac[n], mod - 2);
per (i, n, 1, 1) {
inv[i - 1] = inv[i] * i % mod;
}