乘法逆元及兩道模板題詳解


乘法逆元

就是a\times b\equiv 1(\mod p\left\left\left)

此時b就是a模p意義下的逆元,即inv[a]=b;

下面我們用inv[a]表示a模p意義下的逆元。

逆元是好東西啊

有時候我們需要算出 a/b mod p 的值,用朴素的方法,我們只能在 a 上不斷加 p ,直到它能被 b 整除為止。
當 a,b,p 都很大的時候,這種方法就只能涼涼了,但如果有了逆元,我們就可以非常方便,快捷地求解。                    
 
                                                                      ——————某位大佬的話

所以我先講講逆元性質:

唯一性就不用講了

1.積性

假如a與b互質,inv[a]\times inv[b]\equiv inv[a*b]

2.乘變除

a\times inv[b]\equiv a\div b (\mod p\left\left) 

證明如下:

b\times inv[b]\equiv 1\left ( \mod p \left\left)          

兩邊都乘一個 \frac{a}{b} ,就得到了上面的式子


至於逆元的求法,有很多,我只講四種。

首先,是求單個逆元

1.費馬小定理

當 p 為素數時:

a^{p-1}\equiv 1(\mod p\left\left\left)

a^{p-1}= a\times a^{p-2}

所以

a\times a^{p-2}\equiv 1(\mod p\left\left\left)

所以 a^{p-2}就是a在模p意義下的逆元,快速冪求出 a^{p-2}即可。

此方法的局限就是p只能是質數。

 1 int ksm(int t)
 2 {
 3     if(t==1)return n%p;
 4     if(t%2==0)
 5     return ksm(t>>1)*ksm(t>>1)%p;
 6     else
 7     return ksm(t>>1)*ksm(t>>1)*(n)%p;
 8 }
 9 int main()
10 {
11     scanf("%d%d",&n,&p);
12     printf("%d",ksm(p-2));
13 }

 

2.擴展歐幾里得

a\times inv[a]\equiv 1(\mod p\left\left\left)

變一下,我們把p移到左邊就成了

a\times inv[a]-py= 1

因為y是變量所以可以變成這樣:

a\times inv[a]+py= 1

我們把inv[a]設成x,就變成了擴歐的標准式:

ax+py= 1

就可以瞎搞了,至於不知道怎么瞎搞的同學,可以先點這里exgcd入門以及同余基礎

 1 inline void exgcd(ll a,ll b)
 2 {
 3     if(!b)
 4     {x=1;y=0;return;}
 5     exgcd(b,a%b);
 6     k=x;x=y;
 7     y=k-a/b*y;
 8     return;
 9 }
10 int main()
11 {
12     scanf("%d%d",&n,&p);
13     exgcd(n,p);
14     printf("%d",x);
15 }

然后,就是求多個逆元

1.歐拉函數篩法

在數論,對正整數n,歐拉函數是小於或等於n的正整數中與n互質的數的數目(φ(1)=1)。
                                                         ——————百度百科

我們設\varphi(x)表示小於等於x且與x互質的正整數的個數。

通式如下:

還有歐拉函數有如下幾點性質:

  1. 歐拉函數是積性函數——若m,n互質,
  2. 假如n為奇數,
  3. 假如n為質數,

歐拉函數與費馬小定理有點不明不白的關系

對任何兩個互質的正整數a, m(m>=2)有

這就是歐拉定理

所以類比於費馬小定理的證法:

a^{\phi\left (m \right )}\equiv 1(\mod p\left\left\left)

a^{\phi\left (m \right )}= a\times a^{\phi\left (m \right )-1}

所以a\times a^{\phi(m)-1} \equiv 1(\mod p\left\left\left)

所以我們要求的就是a^{\phi(m)-1},又因為歐拉函數是積性函數,所以把m篩一下質數就好了。

不過有更好的辦法,這里就不寫代碼了(其實不會

2.線性遞推

證明好麻煩啊,那就不證了

反正就是設p= b\times k+a

再往a\times inv[a]\equiv 1(\mod p\left\left\left)

最后得出:-(\frac{p}{a})*inv[p\mod a]\equiv inv[a]

 


講完了,現在講題目

我要講洛谷的兩道題:

1.P3811

這就是模板題,直接套線性遞推,就能ac了

 1 int main()
 2 {
 3     int n,m;
 4     read(n),read(m);
 5     inv[1]=1;
 6     for(register int i=2;i<=n;i++)
 7     inv[i]=(long long)(m-m/i)*inv[m%i]%m;\\加一個m是為了去負
 8     for(register int i=1;i<=n;i++)
 9     printf("%d\n",inv[i]);
10 }

  值得一提的是,這題擴歐卡一卡也可以過(好水

 1 long long k,x,y;
 2 inline void read(long long &x)
 3 {
 4     x=0; 
 5     int f=1; 
 6     char ch=getchar();
 7     while(ch<'0'||ch>'9')
 8     {
 9         if(ch=='-') 
10         f=-1; 
11         ch=getchar();
12     }
13     while(ch>='0'&&ch<='9')
14     {
15         x=x*10+ch-'0';
16         ch=getchar();
17     }x*=f;
18 }
19 inline void exgcd(int a,long long b)
20 {
21     if(b==0)
22     {x=1;y=0;return;}
23     exgcd(b,a%b);
24     k=x;x=y;
25     y=k-a/b*y;
26     return;
27 }
28 int main()
29 {
30     long long n,m;
31     read(n),read(m);
32     for(register int i=1;i<=n;i++)
33     {
34         exgcd(i,m);
35         if(x<=0)x+=m;
36         printf("%lld\n",x);
37     }
38 }

對了,還有一個,費馬小定理算法。

得分48,a了3個,t了3個

 1 inline long long ksm(int t,int i)//記得開long long不然第三個點會wa
 2 {
 3     if(t==1)return i%m;
 4     if(t%2==0)
 5     return ksm(t>>1,i)*ksm(t>>1,i)%m;
 6     else
 7     return ksm(t>>1,i)*ksm(t>>1,i)*(i)%m;
 8 }
 9 int main()
10 {
11     long long n;
12     io::begin();
13     io::read(n);
14     io::read(m);
15     for(register int i=1;i<=n;i++)
16     printf("%lld\n",ksm(m-2,i));
17 }

但是我發現,去掉 inline,就a了4個點,得分64.

所以給大家一個忠告,在遞歸函數下,inline不是一個好的卡常方法!

因為使用內聯函數后雖然調用函數的開銷降低了,但是有利必有弊,內聯函數會導致主函數指令增多、函數體積增大等情況。

這題就這樣

2.P2613

一看數據范圍還是很嚇人的,10^{10001},怕不是要打高精,后來發現不是這樣,不過題解中真有高精算法,大佬!!

首先,先看我最開始引用的話,嗯,有點道理

\frac{a}{b}=a\times b^{-1}

然后解析題目發現:本題就是求b在mod p的意義下的逆元。

所以擴歐一遍過,只是讀入時注意一下邊讀邊模就好了;

 1 inline void exgcd(int a,int b)
 2 {
 3     if(!b)
 4     {x=1;y=0;pos=a;return;}
 5     exgcd(b,a%b);
 6     k=x;x=y;
 7     y=k-a/b*y;
 8     return;
 9 }
10 int main()
11 {
12     std::cin>>a1>>b1;
13     int n=0,m=0;
14     for(register int i=0;i<strlen(a1);i++)
15     n=(n*10+a1[i]-48)%mod;
16     for(register int i=0;i<strlen(b1);i++)
17     m=(m*10+b1[i]-48)%mod;
18     exgcd(m,mod);
19     if(pos!=1)printf("Angry!");
20     else
21     {
22         if(x<0)x+=mod;
23         printf("%lld",(1ll*n*x)%mod);
24     }
25 }

最后通過這道題的大質數給大家念一首詩:苟利國家生死以


免責聲明!

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



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