乘法逆元小結
乘法逆元,一般用於求 $$\frac{a}{b} \pmod p$$ 的值(\(p\) 通常為質數),是解決模意義下分數數值的必要手段。
逆元定義
若\(a*x\equiv1 \pmod {b}\),且\(a\)與\(b\)互質,那么我們就能定義:
\(x\) 為 \(a\) 的逆元,記為\(a^{-1}\),所以我們也可以稱 \(x\) 為 \(a\) 在 \(\bmod b\) 意義下的倒數,
所以對於 \(\displaystyle\frac{a}{b} \pmod {p}\) ,我們就可以求出 \(b\) 在 \(\bmod {p}\) 下的逆元,然后乘上 \(a\) ,再 \(\bmod {p}\),就是這個分數的值了。
求解逆元的方式
拓展歐幾里得
這個方法十分容易理解,而且對於單個查找效率似乎也還不錯,比后面要介紹的大部分方法都要快(尤其對於 \(\bmod {p}\) 比較大的時候)。
這個就是利用拓歐求解 線性同余方程 \(a*x \equiv c \pmod {b}\) 的\(c=1\)的情況。我們就可以轉化為解 \(a*x + b*y = 1\) 這個方程。
求解這個方程的解。不會拓歐可以點這里~
而且這個做法還有個好處在於,當 \(a \bot p\) (互質),但 \(p\) 不是質數的時候也可以使用。
代碼比較簡單:
void Exgcd(ll a, ll b, ll &x, ll &y) {
if (!b) x = 1, y = 0;
else Exgcd(b, a % b, y, x), y -= a / b * x;
}
int main() {
ll x, y;
Exgcd (a, p, x, y);
x = (x % p + p) % p;
printf ("%d\n", x); //x是a在mod p下的逆元
}
快速冪
這個做法要利用 費馬小定理
若\(p\)為素數,\(a\)為正整數,且\(a\)、\(p\)互質。
則有\(a^{p-1} \equiv 1 (\bmod {p})\)。
這個我們就可以發現它這個式子右邊剛好為 \(1\) 。
所以我們就可以放入原式,就可以得到:
所以我們可以用快速冪來算出 \(a^{p-2} \pmod p\)的值,這個數就是它的逆元了
代碼也很簡單:
ll fpm(ll x, ll power, ll mod) {
x %= mod;
ll ans = 1;
for (; power; power >>= 1, (x *= x) %= mod)
if(power & 1) (ans *= x) %= mod;
return ans;
}
int main() {
ll x = fpm(a, p - 2, p); //x為a在mod p意義下的逆元
}
線性算法
用於求一連串數字對於一個\(\bmod p\)的逆元。洛谷P3811
只能用這種方法,別的算法都比這些要求一串要慢。
首先我們有一個,\(1^{-1}\equiv 1 \pmod p\)
然后設 \(p=k*i+r,(1<r<i<p)\) 也就是 \(k\) 是 \(p / i\) 的商,\(r\) 是余數 。
再將這個式子放到\(\pmod p\)意義下就會得到:
然后乘上\(i^{-1}\),\(r^{-1}\)就可以得到:
於是,我們就可以從前面推出當前的逆元了。
代碼也很短:
inv[1] = 1;
for(int i = 1; i < p; ++ i)
inv[i] = (p - p / i) * inv[p % i] % p;
階乘逆元 \(O(n)\) 求
因為有如下一個遞推關系。
\(\displaystyle inv[i+1]=\frac{1}{(i+1)!}\)
\(\displaystyle inv[i+1]*(i+1)=\frac{1}{i!}=inv[i]\)
所以我們可以求出\(n!\)的逆元,然后逆推,就可以求出\(1...n!\)所有的逆元了。
遞推式為
\(inv[i+1]*(i+1)=inv[i]\)
所以我們可以求出 \(\displaystyle \forall i, i!,\frac{1}{i!}\) 的取值了。
然后這個也可以導出 \(\displaystyle \frac{1}{i} \pmod p\) 的取值,也就是
具體實現可以參考我這發提交(卡了常。。)