乘法逆元
逆元,一般用于求\(\frac{a}{b} (\bmod {p})\)。
这种东西,我看到都是十分懵的,原来一直不打算去学习,可现实十分残酷,
导致很多次考试都很被动,我下定决心学一学2333。
逆元定义
若\(a*x\equiv1 (\bmod {b})\),且\(a\)与\(b\)互质,那么我们就能定义:
\(x\)为\(a\)的逆元,记为\(a^{-1}\),所以我们也可以称\(x\)为\(a\)的倒数,
上列计算都在$\bmod b $意义下。
所以对于\(\frac{a}{b} (\bmod {p})\),我们就可以求出\(b\)在\(\bmod {p}\)
下的逆元,然后乘上\(a\),再\(\bmod {p}\),就是这个乘法逆元的值了。
算逆元的方法
- 拓展欧几里得
这个方法十分容易理解,而且对于单个查找效率似乎也还不错,比后面要介绍的
快速幂的方法大部分要快(尤其对于\(\bmod {p}\)比较大的时候)。
这个就是利用拓欧求解 线性同余方程\(a*x \equiv c (\bmod {b})\)
的\(c=1\)的情况。我们就可以转化为\(a*x + b*y = 1 (\bmod {b})\)
求解这个方程的解。
代码比较简单:
void exgcd (ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1;
y = 0;
return ;
}
exgcd (b, a % b, x, y);
ll tmp = x;
x = y;
y = tmp - a / b * y;
}
ll x, y;
exgcd (i, p, x, y);
x = (x % p + p) % p;
printf ("%d\n", x);
- 快速幂
这个做法要利用 费马小定理
若\(p\)为素数,\(a\)为正整数,且\(a\)、\(p\)互质。
则有\(a^{p-1} \equiv 1 (\bmod {p})\)。
这个我们就可以发现它这个式子右边刚好为1。
所以我们就可以放入原式,就可以得到:
\(a*x\equiv 1 (\bmod {b})\)
\(a*x\equiv a^{p-1} (\bmod {b})\)
\(x \equiv a^{p-2} (\bmod {b})\)
所以我们可以用快速幂来算出 \(a^{p-2} (\bmod {b})\)的值,这个数就是它的逆元了
代码也很简单:
ll fpm(ll x, ll power, ll mod) {
x %= mod;
ll ans = 1;
while (power) {
if (power & 1) ans = (ans * x) % mod;
x = (x * x) % mod;
power >>= 1;
}
return ans;
}
printf ("%lld\n", fpm(i, p-2, p) );
- 线性算法
用于求一连串数字对于一个\(\bmod p\)的逆元。洛谷P3811
只能用这种方法,别的算法都比这些要求一串要慢。
首先我们有一个,\(1^{-1}\equiv 1(\bmod p)\)
然后设\(p=k*i+r,r<i,1<i<p\),再将这个式子放到\(\bmod {p}\)意义下就会得到:
\(k*i+r \equiv 0 (\bmod p)\)
然后乘上\(i^{-1}\),\(r^{-1}\)就可以得到:
\(k*r^{-1}+i^{-1}\equiv 0 (\bmod p)\)
\(i^{-1}\equiv -k*r^{-1} (\bmod p)\)
\(i^{-1}\equiv -\lfloor \frac{p}{i} \rfloor*(p \bmod i)^{-1} (\bmod p)\)
于是,我们就可以从前面推出当前的逆元了。代码也就两行:
a[i] = - (p / i) * a[p % i];
a[i] = (a[i] % p + p) % p;
阶乘逆元\(O(n)\)求
证明:
\(inv[i+1]=\frac{1}{(i+1)!}\)
\(inv[i+1]*(i+1)=\frac{1}{i!}=inv[i]\)
所以我们可以求出\(n!\)的逆元,然后逆推,就可以求出\(1...n!\)所有的逆元了。
递推式为
inv[i+1]*(i+1)=inv[i]