數論學習_逆元意義及求法


首先說明逆元的概念,類似於倒數的性質。

方程ax≡1(mod  p),的解稱為a關於模p的逆,當gcd(a,p)==1(即a,p互質)時,方程有唯一解,否則無解。

對於一些題目會要求把結果MOD一個數,通常是一個較大的質數,對於加減乘法通過同余定理可以直接拆開計算,

但對於(a/b)%MOD這個式子,是不可以寫成(a%MOD/b%MOD)%MOD的,但是可以寫為(a*b^-1)%MOD,其中b^-1表示b的逆元。

知道了逆元的作用,接下來就是逆元的求法。

首先,有一個費馬小定理:

費馬小定理(Fermat's little theorem)數論中的一個重要定理,在1636年提出,其內容為: 假如p是質數,且gcd(a,p)=1,那么 a(p-1)≡1(mod p),即:假如a是整數,p是質數,且a,p互質(即兩者只有一個公約數1),那么a的(p-1)次方除以p的余數恆等於1。

意思很明了,由上方的公式很容易推導出:a*a(p-2)≡1(mod p)對於整數a,p,a關於p的逆元就是a^(p-2),直接快速冪解之即可,但注意這個定理要求a,p互質!

下面以HDU5685為例

http://acm.hdu.edu.cn/showproblem.php?pid=5685

題目給出的hash計算法就是連續區間元素的乘積對9973取模,我們自然而然想到前綴積的方法,但是數很大,前綴積顯然只能存下%9973后得值,

但有:(A/B)%M=(A%M*(B^-1%M))%M,A%M顯然就是pre[A],B的逆對M取余就是qpow(B,M-2,M),快速冪模之即可。

 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define LL int
 4 #define MOD 9973
 5 LL inv[10000];
 6 LL pre[100005];
 7 char s[100005];
 8 LL qpow(LL a,LL b,LL M){
 9 LL r=1;
10 while(b){
11     if(b&1) r=r*a%M;
12     b>>=1;
13     a=a*a%M;
14     }
15     return r;
16 }
17 int main()
18 {
19     int N,i,j,k;
20     inv[1] = 1;
21     for(i=2;i<=9972;++i) inv[i]=qpow(i,MOD-2,MOD);
22     while(scanf("%d",&N)==1){pre[0]=1;
23         scanf("%s",s+1);
24         int n=strlen(s+1);
25         for(i=1;i<=n;++i){
26             pre[i]=pre[i-1]*(s[i]-28)%MOD;
27         }
28         while(N--){int a,b;
29             scanf("%d%d",&a,&b);
30             printf("%d\n",pre[b]*inv[pre[a-1]]%MOD);
31         }
32     }
33     return 0;
34 }

 還有一種線性遞推的方法:

   inv[1] = 1;
    for(i=2;i<MOD;++i) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;  (isprime(MOD))

對於這個式子的證明:

  M=k*i+r≡0   (mod M)

式子兩邊同乘上 i-1*r-1,如果M不是質數得話r就可能為零

              k*r-1+i-1≡0         (mod M)

             i-1≡M-floor(M/i)*(M mod i)-1    (mod M)

易證 <==> i-1=(M-M/i)*inv[M%i]%M;

 

還有就是拓展歐幾里得算法,對於一些很大的數,用遞推法非常耗時,但又不適用於快速冪的時候(可能會爆long long 無法得出正確結果),

考慮使用這個算法。

我們對這個算法進行推演一下,exgcd求解的是一組   ax+by=gcd(a,b) 的一組(x,y)的解,

我們設 

                 ax1+by1=gcd(a,b)-----------------------1  

                 bx2+(a%b)*y2=gcd(b,a%b)------------2       

根據朴素的歐幾里得算法可得 1,2式的右端是相等的,同理左端也相等,

         ax1+by1=bx2+(a%b)*y2

==>  ax1+by1=bx2+(a-a/b*b)*y2

==>ax1+by1=ay2+b(x2-a/b*y2)

顯然我們能得出  x1=y2;    y1=x2-a/b*y2;

也就是說我們根據x2,y2便可推導出x1,y1的值,只要將a,b換成b,a%b即可,和gcd算法類似這是一個遞歸的過程,出口當b==0時,此時 x=1,y=0,gcd(a,b)=d=a;

對於ax+by=gcd(a,b)這個式子,當gcd(a,b)=1的時候,ax+by=1,根據逆元的定義可知x的解就是a關於模b的逆元,y的解就是b關於模a的逆元,當gcd(a,b)=d=1時成立。

(ax+by=1 -->  ax(mod b)=(1-by) (mod p)=1 (mod p) )

這是gcd關於遞推法的比較答案顯然一致

 1 #include<iostream>
 2 using namespace std;
 3 #define LL long long
 4 #define mod 997
 5 void gcd(LL a,LL b,LL &d,LL &x,LL &y)
 6 {
 7     if(!b) {d=a;x=1;y=0;}
 8     else {gcd(b,a%b,d,y,x);y-=x*(a/b);}
 9 }
10 LL finv(LL a,LL n)
11 {
12   LL d,x,y;
13   gcd(a,n,d,x,y);
14   return d==1?(x+n)%n:-1;
15 }
16 int main()
17 {
18     int inv[100]={1,1};
19     for(int i=2;i<=99;++i){
20         inv[i]=(mod-mod/i)*inv[mod%i]%mod;
21         cout<<inv[i]<<" "<<finv(i,mod)<<endl;
22     }
23     return 0;
24 }

 對於逆元運算還有一些常見的式子,如:

                                                           1.(a^(-1))^(-1)=a;
                                                           2.(a*b)^(-1)=b^(-1)*a^(-1)

對他們的證明:

設單位元為e.
 x的逆元按定義是滿足x·y = y·x = e的元素y.
逆元是唯一的:設x·y = y·x = e,x·z = z·x = e,有y = y·e = y·(x·z) = (y·x)·z = e·z = z.
1.a^(-1)是a的逆元,即有a·a^(-1) = a^(-1)·a = e,也即a^(-1)·a = a·a^(-1) = e.
於是a也是a^(-1)的逆元,可寫為(a^(-1))^(-1) = a.
2.(a·b)·(b^(-1)·a^(-1)) = a·(b·(b^(-1))·a^(-1) = a·e·a^(-1) = a·a^(-1) = e.
(b^(-1)·a^(-1))·(a·b) = b^(-1)·(a^(-1)·a)·b = b^(-1)·e·b = b^(-1)·b = e.
於是b^(-1)·a^(-1)是a·b的逆元,即有b^(-1)·a^(-1) = (a·b)^(-1).


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM