友情提示:
Latex加載稍慢,請耐心等待
什麼是逆元?
若$x$滿足
$a*x\equiv 1(\mod p)$
我們稱$x$是$a$在$\mod p$意義下的逆元
逆元的基本解法
https://loj.ac/problem/110
1.快速幂
當p為素數
根據費馬小定理
$a^{(p-1)}\equiv 1(mod p)$
${\color{Green}a*a^{(p-2)}\equiv 1(mod p) }$
帶入快速幂就好啦
時間複雜度:$O(log_2^p)$
1 #include<cstdio>
2 #define LL long long
3 using namespace std;
4 const LL MAXN=200000001;
5 LL n,mod;
6 LL fastpow(LL val,LL p)
7 {
8 LL base=1;
9 while(p)
10 {
11 if(p&1) base=(base*val)%mod;
12 val=(val*val)%mod;
13 p>>=1;
14 }
15 return base;
16 }
17 int main()
18 {
19 scanf("%lld%lld",&n,&mod);
20 for(LL i=1;i<=n;i++)
21 printf("%lld\n",fastpow(i,mod-2)%mod);
22 return 0;
23 }
2.擴充歐幾裡得
對于$a*x\equiv 1(mod p)$
他的另一種寫法為
$a*x+p*y=1$(想一想,為什麼)
擴充歐幾裡得,帶入求解
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 using namespace std;
5 int n,mod;
6 inline int read()
7 {
8 char c=getchar();int flag=1,x=0;
9 while(c<'0'||c>'9') {if(c=='-') flag=-1;c=getchar();}
10 while(c>='0'&&c<='9') x=x*10+c-48,c=getchar(); return x*flag;
11 }
12 int x,y;
13 int gcd(int a,int b)
14 {
15 return b==0?a:gcd(b,a%b);
16 }
17 int exgcd(int a,int b,int &x,int &y)
18 {
19 if(b==0)
20 {
21 x=1,y=0;
22 return a;
23 }
24 int r=exgcd(b,a%b,x,y);
25 int tmp=x;x=y;y=tmp-(a/b)*y;
26 return r;
27 }
28 int main()
29 {
30 n=read(),mod=read();
31 for(int i=1;i<=n;i++)
32 {
33 int g=exgcd(i,mod,x,y);
34 while(x<0) x+=mod;
35 printf("%d\n",x);
36 }
37 return 0;
38 }
時間複雜度:$O(log_2^n)$
3.遞推
前兩種方法常用來求單個逆元
對于逆元的需要量比較大的時候,我們可以使用遞推的方法來求逆元
前提條件:$P$為素數
推導過程
設$t=P/i$
$k=P \mod i$
顯然有
$t*i+k \equiv 0 (\mod P)$
$k \equiv -t*i(\mod P)$
兩側同除$i*k$,把$t$和$k$帶入
$inv[i] \equiv -p/i*inv[p \mod i] (\mod p)$
這裡需要注意一個事情,
對于 $a\mod p$當$a<0$時,
應為$(a+p) \mod p$
這樣就可以把原式的$\mod p$消掉,得
$inv[i]=P-P/i*inv[P\mod i]$
這樣就可以進行線性的遞推啦
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<cmath>
5 #define LL unsigned long long
6 using namespace std;
7 const LL MAXN=200000001;
8 inline LL read()
9 {
10 char c=getchar();LL flag=1,x=0;
11 while(c<'0'||c>'9') {if(c=='-') flag=-1;c=getchar();}
12 while(c>='0'&&c<='9') x=x*10+c-48,c=getchar(); return x*flag;
13 }
14 LL inv[MAXN];
15 LL n,p;
16 int main()
17 {
18 n=read(),p=read();
19 inv[1]=1;
20 printf("1\n");
21 for(int i=2;i<=n;i++)
22 {
23 inv[i]=(p-p/i)*inv[p%i]%p;
24 printf("%d\n",inv[i]);
25 }
26 return 0;
27 }
時間複雜度:$O(n)$
總結
在求多個數的逆元的時候,推薦使用遞推算法
在求單個數的逆元的時候,推薦使用擴充歐幾裡得算法
因為擴充歐幾裡得算法不受模數的限制,而且自測運作效率比快速幂高不少
作者:自為風月馬前卒
個人部落格http://attack204.com//
出處:http://zwfymqz.cnblogs.com/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。