1. 程式人生 > >數論文章----關於逆元的求法(尤拉定理,階乘逆元,費馬小定理,模質數p的情況)

數論文章----關於逆元的求法(尤拉定理,階乘逆元,費馬小定理,模質數p的情況)

乘法逆元

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

  1. typedef long long ll;

  2. void extgcd(ll a,ll b,ll& d,ll& x,ll& y){

  3. if(!b){ d=a; x=1; y=0;}

  4. else{ extgcd(b,a%b,d,y,x); y-=x*(a/b); }

  5. }

  6. ll inverse(ll a,ll n){

  7. ll d,x,y;

  8. extgcd(a,n,d,x,y);

  9. return d==1?(x+n)%n:-1;

  10. }

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比較大的時候需要用快速冪求解

  1. typedef long long ll;

  2. ll pow_mod(ll x, ll n, ll mod){

  3. ll res=1;

  4. while(n>0){

  5. if(n&1)res=res*x%mod;

  6. x=x*x%mod;

  7. n>>=1;

  8. }

  9. return res;

  10. }

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

a^phi(p)≡1               (mod p)

a*a^(phi(p)-1)≡1      (mod p)

a^(-1)≡a^(phi(p)-1)  (mod p)

所以
時間複雜度即求出單個尤拉函式的值

(當p為素數的時候phi(p)=p-1,則phi(p)-1=p-2可以看出尤拉定理是費馬小定理的推廣)

PS:這裡就貼出尤拉定理的板子,很少會用尤拉定理求逆元

3.特殊情況

一:

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

求解就灰常方便了…

二:

求逆元一般公式(條件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模奇素數的所有逆元了。

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

  1. typedef long long ll;

  2. const int N = 1e5 + 5;

  3. int inv[N];

  4. void inverse(int n, int p) {

  5. inv[1] = 1;

  6. for (int i=2; i<=n; ++i) {

  7. inv[i] = (ll) (p - p / i) * inv[p%i] % p;

  8. }

  9. }

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