乘法逆元
定义
如果一个线性同余方程 \(ax\equiv 1\pmod b\) ,则称 \(x\) 为 \(a\bmod b\) 的逆元,记作 \(a^{-1}\)
它等价于 \(ax+by=1\) ,根据线性同余方程有解的条件可得 \(\gcd(a,b)\mid 1\) ,所以当且仅当 \(\gcd(a,b)=1\) 时 \(a\) 在模 \(b\) 意义下存在逆元
计算
快速幂
当 \(b\) 为素数时,由费马小定理可得 \(a^{b-1}\equiv1\pmod b\) ,那么 \(x=a^{b-2}\) 就是 \(a\) 的逆元,可以用快速幂以 \(O(\log b)\) 求解
#define ll long long
ll qpow(ll a, ll b, ll p)
{
ll res = 1;
for(; b; b >>= 1) {
if(b & 1)
res = res * a % p;
a = a * a % p;
}
return res;
}
ll inv(ll a, ll b)
{
return qpow(a, b - 2, b);
}
扩展欧几里得
求逆元实际上等价于求解一个线性同余方程,所以可以用扩展欧几里得算法以 \(O(\log b)\) 求解,并且这不要求 \(b\) 为素数,只要求 \(\gcd(a,b)=1\)
#define ll long long
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if(b == 0) {
x = 1;
y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
ll inv(ll a, ll b)
{
ll x, y;
exgcd(a, b, x, y);
return (x % b + b) % b;
}
线性求逆元
当我们要求出 \(1,2,\cdots,n\) 中每个数关于 \(p\) 的逆元时,用上述两个方法的复杂度为 \(O(n\log p)\) ,下面的方法能以 \(O(n)\) 求出这 \(n\) 个数的逆元
令 \(k=\lfloor\frac{p}{i}\rfloor ,j=p \bmod i\) ,有 \(p=ki+j\) ,则 \(ki+j\equiv 0\pmod{p}\)
最后一步把 \(k\) 换成 \((p-\lfloor\frac{p}{i}\rfloor)\) 而非 \(\lfloor\frac{p}{i}\rfloor\) 的目的是为了避免出现负数,这样我们就得到了求 \(1\sim n\) 的逆元的递推式:
int inv[100000 + 5];
void init(int n, int p)
{
inv[1] = 1;
for(int i = 2; i <= n; i++)
inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
}
阶乘的逆元
设 \(f_i=i!\) ,可得:
所以先用线性求逆元得方法预处理出 \(1,2,\cdots,n\) 的逆元,就可以递推出 \(1,2,\cdots,n\) 的阶乘的逆元,复杂度为 \(O(n)\)
int inv[100000 + 5];
int invf[100000 + 5];
void init(int n, int p)
{
inv[1] = invf[0] = 1;
for(int i = 2; i <= n; i++)
inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
for(i = 1; i <= n; i++)
invf[i] = 1ll * invf[i - 1] * inv[i] % p;
}