乘法逆元笔记总结
乘法逆元
含義
首先,數學上的乘法逆元就是指直觀的倒數,即 aaa 的逆元是 1a\displaystyle\frac{1}{a}a1? ,也即與 aaa 相乘得 111 的數。ax=1ax=1ax=1,則 xxx 是 aaa 的乘法逆元。
如果到度娘上搜索逆元,會看到如下定義:(這里逆元素是逆元的全稱)
逆元素是指一個可以取消另一給定元素運算的元素 。
這個定義似乎不太那么直觀……
我們來舉個例子吧,先再實數范圍舉例,由小學知識可知,如果一個代數式 FFF 乘一個數 aaa 后,再乘它的倒數 1a\frac 1 aa1? ,相當于沒有乘 aaa (這里不考慮 000 的情況),換句話說,我們乘 1a\frac 1 aa1? 后,取消了代數式 FFF 乘 aaa 后值增大的影響。
不難發現這符合逆元的定義,故我們可以說一個數和其倒數互為乘法逆元。
而算法這里這里我們討論關于取模運算的乘法逆元,即對于整數 aaa ,與 aaa 互質的數 bbb 作為模數,當整數 xxx 滿足 axmodb≡1ax\mod\,b≡1axmodb≡1 時,稱 xxx 為 aaa 關于模 bbb 的逆元,代碼表示是 a * x % b == 1 。
我們通常將 aaa 的逆元記作 a?1a^{-1}a?1 。
在算法競賽中,經常會遇到求解數據很大,則輸出模 109+710^9+7109+7 的解這類要求。加法、減法、乘法等操作,基于同余理論直接取模即可。但遇到除法時,某步中間結果不一定能完成整除,就無法求解了。
舉個栗子:
求3 * 6 / 3 對7 取模的結果。我們直接算出3 * 6 / 3的結果是6,對7取模得最終答案是6 。
但我們通常面對的問題是中間結果超過int甚至long long的范圍,而不得不在每一步基于同余理論取模,我們用這個例子嘗試一下:
還是求 3 * 6 / 3 % 7
第一步:3 * 6 == 18,18 % 7 == 4
第二步:4 這個中間結果再做 4 / 3 無法整除,就無法進行下去了。
但我們可以求出除數3 關于模數7的逆元5(根據逆元定義,5 符合3 * 5 % 7 == 1),從而,用乘以5代替除以3。
上述第二步除法變乘法:4 * 5 == 20,20 % 7 == 6
從而也計算出了正確的結果6 。
故而我們需要一個算法求 除數 的 取模逆元 ,從而在四則運算取模的任務中,用逆元將除法轉為乘法。
求法
1.費馬小定理
前置芝士:歐拉定理:若a,na,na,n互質,則a?(n)≡1(modn)a^{\phi(n)}\equiv 1 (\mod n)a?(n)≡1(modn)。
費馬小定理:當有兩數 aaa, ppp 滿足 gcd?(a,p)=1gcd ? ( a , p ) = 1gcd?(a,p)=1, 并且 ppp 是質數時,則有
ap?1≡1(modp)\begin{aligned} a^{p-1}\equiv 1\,\;(\mod p\;) \end{aligned}ap?1≡1(modp)?
將該等式與 ax≡1(modp)ax\equiv 1(\mod p)ax≡1(modp) 對比可以驚奇地發現:
x=ap?2\begin{aligned} x=a^{p-2} \end{aligned}x=ap?2?
逆元求出來之后,就可以用 快速冪 求出 ap?2a^{p-2}ap?2 的值了;
時間復雜度:大約 O(logp)O(log p)O(logp) 。
這里沒啥好貼的代碼了,貼一個快速冪吧:
//計算a^k%p int qmi(int a,int k,int p){int res=1;while(k){if(k&1) res=(LL)res*a%p;k>>=1;a=(LL)a*a%p;}return res; }2.擴展歐幾里得
前置芝士:裴蜀定理:對于任意的正整數 a,ba,ba,b 一定存在正整數 x,yx,yx,y 使得 ax+by=gcd(a,b);ax+by=gcd(a,b);ax+by=gcd(a,b); (并且也是能湊出來的最小正整數)
由定義可知:ax≡1(modp)ax\equiv 1\,(\mod p)ax≡1(modp),這個式子等價于已知 a,pa,pa,p 求一個二元一次不定方程 ax=kp+1ax=kp+1ax=kp+1,移一下項得:ax?kp=1ax?kp=1ax?kp=1。這東西與 ax+by=gcd(a,b)ax+by=gcd(a,b)ax+by=gcd(a,b) 不是神似?就是擴展歐幾里得算法。
附一小段擴展歐幾里得代碼:
//返回gcd(a,b),計算x,y int exgcd(int a,int b,int &x,int &y){if(!b){x=1,y=0;return a;}int d=exgcd(b,a%b,y,x);y-=a/b*x;return d; }相比于費馬小定理,擴展歐幾里得的使用限制更小,不再需要 ppp 是質數,只需要 a,pa,pa,p 互質即可。
時間復雜度:大約 O(logn)O(logn)O(logn) 。
3.遞推計算階乘的逆元
當我們要計算一大串連續的階乘的逆元時,采用費馬小定理或擴展歐幾里得算法就有可能超時,所以我們必須采用一個更快的算法。
令 fi=i!f_i=i!fi?=i! ,則可得:
inv(fi)≡inv(fi?1?(i))(modp)\begin{aligned} inv(f_{i})\equiv inv(f_{i-1}\cdot (i))(\mod p) \end{aligned}inv(fi?)≡inv(fi?1??(i))(modp)?
我們將右式乘開,則有:
inv(fi)≡inv(fi?1)?i?1(modp)\begin{aligned} inv(f_{i})\equiv inv(f_{i-1})\cdot i^{-1}(\mod p) \end{aligned}inv(fi?)≡inv(fi?1?)?i?1(modp)?
最后我們每一次只用處理i這一個數的逆元了。
下面是一小段預處理代碼(提前打表):
//fact[i]表示i的階乘,infact[i]表示i的階乘的逆元。 fact[0]=infact[0]=1;for(int i = 1;i < N;i ++ ){fact[i]=(LL)fact[i - 1]*i%mod;infact[i]=(LL)infact[i - 1]*qmi(i,mod - 2,mod)%mod;}4.遞推計算連續的數的逆元 (線性求逆)
當我們要計算連續的數的逆元時,我們可以采用以下遞推式:
inv(i)=(p??pi?)×inv(pmodi)modp\begin{aligned} inv(i)=(p-\lfloor\frac{p}{i}\rfloor)\times inv(p\mod i)\mod p \end{aligned}inv(i)=(p??ip??)×inv(pmodi)modp?
證明:設 t=?pi?,k=pmodit=\lfloor\frac{p}{i}\rfloor,k=p\mod it=?ip??,k=pmodi ,那么顯然有
t×i+k≡0(modp)\begin{aligned}t\times i+k\equiv 0(\mod p)\end{aligned}t×i+k≡0(modp)?
變形可得
?t×i≡k(modp)\begin{aligned} -t\times i\equiv k(\mod p) \end{aligned}?t×i≡k(modp)?
兩邊同時除以 ikikik 得到
?t×1k≡1i(modp)\begin{aligned} -t\times\frac{1}{k}\equiv\frac{1}{i}(\mod p) \end{aligned}?t×k1?≡i1?(modp)?
即
inv(i)≡?t×inv(k)(modp)\begin{aligned} inv(i)\equiv-t\times inv(k)(\mod p) \end{aligned}inv(i)≡?t×inv(k)(modp)?
所以有
inv(i)=(p??pi?)×inv(pmodi)modp\begin{aligned} inv(i)=(p-\lfloor\frac{p}{i}\rfloor)\times inv(p\mod i)\mod p \end{aligned}inv(i)=(p??ip??)×inv(pmodi)modp?
參考代碼:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int maxn = 3e6 + 10; long long infact[maxn], n, p;int main() {scanf("%lld%lld", &n, &p);infact[0] = 0, infact[1] = 1;//初始化1的逆元為1printf("%lld\n", infact[1]);for (int i = 2; i <= n; ++i){infact[i] = (p - p / i) * infact[p % i] % p;//上文中的式子printf("%lld\n", infact[i]);}return 0; }總結
- 上一篇: 业界 !从未卜先知的信号灯说起,阿里城市
- 下一篇: 高利润赚钱行业有哪些?300的利润使人疯