乘法逆元的幾種求法總結
乘法逆元
對於縮系中的元素,每個數a均有唯一的與之對應的乘法逆元x,使得ax≡1(mod n)
一個數有逆元的充分必要條件是gcd(a,n)=1,此時逆元唯一存在
逆元的含義:模n意義下,1個數a如果有逆元x,那麼除以a相當於乘以x。
下面給出求逆元的幾種方法
1 迴圈找解法
給定模m和需要求逆的數x,直接暴力列舉1~m-1
檢查是否有x*i=1(mod m)
這種演算法可以應用與寫暴力、對拍、模數較小,求逆次數少的情況
時間複雜度O(m)
2 擴充套件歐幾里得演算法
給定模數m,求a的逆相當於求解ax=1(mod m)
這個方程可以轉化為ax-my=1
然後套用求二元一次方程的方法,用擴充套件歐幾里得演算法求得一組x0,y0和gcd
檢查gcd是否為1
gcd不為1則說明逆元不存在
若為1,則調整x0到0~m-1的範圍中即可
void ext_gcd(int a,int b,int &d,int &x,int &y){
if (b==0){
d=a;x=1;y=0;
return;
}
ext_gcd(b,a%b,d,y,x);//回代
y-=a/b*x;
}
int main(){
scanf("%d%d",&m,&n);//求m關於n的逆元
ext_gcd(m,n,d,x,y);
printf("%d\n",(x+n)%n);
return 0;
}
這種演算法效率較高,常數較小,時間複雜度為O(ln n)
3 費馬小定理及尤拉定理
在模為素數p的情況下,有費馬小定理
a^(p-1)=1(mod p)
那麼a^(p-2)=a^-1(mod p)
也就是說a的逆元為a^(p-2)
而在模不為素數p的情況下,有尤拉定理
a^phi(m)=1(mod m) (a⊥m)
同理a^-1=a^(phi(m)-1)
因此逆元x便可以套用快速冪求得了x=a^(phi(m)-1)
但是似乎還有個問題?如何判斷a是否有逆元呢?
再求一次gcd判斷是否互質嗎?
這還不如直接用擴充套件歐幾里得演算法呢>_<
其實問題很簡單,直接判斷這是不是逆元就行了:
檢驗逆元的性質,看求出的冪值x與a相乘是否為1即可
這種演算法複雜度為O(log2 n)
在幾次測試中,常數似乎較上種方法大
4 O(n)求1~n逆元表
有時會遇到這樣一種問題,
在模質數p下,求1~n逆元 n< p
這個問題有種很牛的演算法,其基於以下的推導:
在求i的逆元時
p%i+[p/i]*i=p
令a=p%i,b=[p/i],則有
a+b*i=p
a+b*i=0(mod p)
b*i=-a(mod p)
i^-1=-b/a
也就是說i的逆元為:-[p/i]*(p%i)^-1
而p%i<i,那麼可以從2遞推到n求逆元,在求i之前p%i一定已經求出
這樣就可以O(n)求出所有逆元了
(初始化 1^(-1)=1)
程式碼如下
inv[1]=1;
fo(i,2,n)
inv[i]=(long long)(p-p/i)*inv[p%i]%p;
除了上面這種演算法之外,還有幾種O(n)的逆元表求法,不過沒有上面這種方法簡便。
A 可以先O(n)預處理出1!~n! 然後用O(log n)的複雜度求出n!的逆元
然後就可以O(n)處理出i!的逆元 (1/i!=(i+1)/(i+1)! 倒推即可)
接下來就可以O(n)求出所有i的逆元 1/i=(i-1)!/i!
需要同時預處理階乘 階乘的逆元的題目可以採用這種方法求逆元表。
這種方法擴充套件性比較強,可以用這種方法求出一個序列中所有元素(元素字首積)的逆元,複雜度也是O(n),比一個個單獨計算逆元效率更優。
B 可以用線性篩法。由於逆元是積性函式,合數的逆元可以O(1)計算,質數的逆元可以用O(log n)的複雜度求。由於n以內質數大約有n/log n個。總複雜度也是O(n)
除錯問題
最後再簡單講講如何除錯,檢測求得的逆元是否正確
還是同上面所說的那樣,直接測試其與原數相乘是否為1即可了
這裡順便再囉嗦一句:如何檢驗快速冪演算法是否正確呢(這還用檢驗)
隨便找1個質數p,檢測a^(p-1)%p是否都為1就行啦 (1<=a<p)