1. 程式人生 > >乘法逆元的幾種求法總結

乘法逆元的幾種求法總結

乘法逆元

對於縮系中的元素,每個數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)