1. 程式人生 > >逆元的幾種求法(擴充套件歐幾里得,費馬小定理或尤拉定理,特例,打表等)

逆元的幾種求法(擴充套件歐幾里得,費馬小定理或尤拉定理,特例,打表等)

乘法逆元

對於縮系中的元素,每個數a均有唯一的與之對應的乘法逆元x,使得ax≡1(mod n)
一個數有逆元的充分必要條件是gcd(a,n)=1,此時逆元唯一存在
逆元的含義:模n意義下,1個數a如果有逆元x,那麼除以a相當於乘以x。

下面給出求逆元的幾種方法:
1.擴充套件歐幾里得

給定模數m,求a的逆相當於求解ax=1(mod m)
這個方程可以轉化為ax-my=1
然後套用求二元一次方程的方法,用擴充套件歐幾里得演算法求得一組x0,y0和gcd
檢查gcd是否為1
gcd不為1則說明逆元不存在
若為1,則調整x0到0~m-1的範圍中即可

PS:這種演算法效率較高,常數較小,時間複雜度為O(ln n)


typedef  long long ll;
void extgcd(ll a,ll b,ll& d,ll& x,ll& y){
    if(!b){ d=a; x=1; y=0;}
    else{ extgcd(b,a%b,d,y,x); y-=x*(a/b); }
}
ll inverse(ll a,ll n){
    ll d,x,y;
    extgcd(a,n,d,x,y);
    return d==1?(x+n)%n:-1;
}

2.費馬小定理

在模為素數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是否有逆元呢? 

檢驗逆元的性質,看求出的冪值x與a相乘是否為1即可

PS:這種演算法複雜度為O(log2N)在幾次測試中,常數似乎較上種方法大

當p比較大的時候需要用快速冪求解

typedef  long long ll;
ll pow_mod(ll x, ll n, ll mod){
    ll res=1;
    while(n>0){
        if(n&1)res=res*x%mod;
        x=x*x%mod;
        n>>=1;
    }
    return res;
}

當模p不是素數的時候需要用到尤拉定理

a^phi(p)≡1               (mod p) a*a^(phi(p)-1)≡1      (mod p)
a^(-1)≡a^(phi(p)-1)  (mod p)
所以aϕ(m)1a
時間複雜度O(n)即求出單個尤拉函式的值
(當p為素數的時候phi(p)=p-1,則phi(p)-1=p-2可以看出尤拉定理是費馬小定理的推廣) PS:這裡就貼出尤拉定理的板子,很少會用尤拉定理求逆元
3.特殊情況

一:

當N是質數a(N+1)a1=N+1a 
這點也很好理解。當N是質數,0 < a < N時,(a,N)=1,則a肯定存在逆元。 
而解出的N+1a就滿足N+1aa1(modN),故它是a的逆元。

CF 696CN=1000000007

21=1000000007+12=500000004 31=1000000007+13=333333336 求解就灰常方便了… 二: 求逆元一般公式(條件b|a)

ans=a/bmodm=amod(mb)/b

公式證明:


PS:實際上a mod (bm)/b這種的對於所有的都適用,不區分互不互素,而費馬小定理和擴充套件歐幾里得演算法求逆元是有侷限性的,它們都會要求互素,如果a與m不互素,那就沒有逆元,這個時候需要a mod (bm)/b來搞(此時就不是逆元的概念了)。但是當a與m互素的時候,bm可能會很大,不適合套這個一般公式,所以大部分時候還是用逆元來搞
4.逆元打表

有時會遇到這樣一種問題,在模質數p下,求1~n逆元 n< p(這裡為奇質數)。可以O(n)求出所有逆元,有一個遞推式如下

                   

它的推導過程如下,設,那麼

       

對上式兩邊同時除,進一步得到

       

再把替換掉,最終得到

       

初始化,這樣就可以通過遞推法求出1->n模奇素數的所有逆元了。

另外有個結論的所有逆元值對應中所有的數,比如,那麼對應的逆元是

typedef  long long ll;
const int N = 1e5 + 5;
int inv[N];
 
void inverse(int n, int p) {
    inv[1] = 1;
    for (int i=2; i<=n; ++i) {
        inv[i] = (ll) (p - p / i) * inv[p%i] % p;
    }
}

例題可以參考http://blog.csdn.net/acdreamers/article/details/8220787