学习笔记:求逆元的三种方法
前言
对于模运算来说:
(
a
+
b
)
m
o
d
p
=
(
a
m
o
d
p
+
b
m
o
d
p
)
m
o
d
p
(a+b)\mod p=(a\mod p+b\mod p)\mod p
(a+b)modp=(amodp+bmodp)modp
(
a
−
b
)
m
o
d
p
=
(
a
m
o
d
p
+
b
m
o
d
p
)
m
o
d
p
(a-b)\mod p=(a\mod p +b\mod p)\mod p
(a−b)modp=(amodp+bmodp)modp
(
a
×
b
)
m
o
d
p
=
(
a
m
o
d
p
×
b
m
o
d
p
)
m
o
d
p
(a\times b)\mod p=(a\mod p \times b\mod p)\mod p
(a×b)modp=(amodp×bmodp)modp
a
b
m
o
d
p
=
(
a
m
o
d
p
)
b
m
o
d
p
a^b\mod p=(a\mod p)^b\mod p
abmodp=(amodp)bmodp
我们发现模运算不满足除法。
那么如果要对分数取模的话,我们就要使用逆元。
逆元就是在模意义下的倒数,但并不是就是倒数!逆元可以表示为:
a
b
m
o
d
p
=
a
×
i
n
v
(
b
)
m
o
d
p
\frac {a}{b}\mod p =a\times inv(b)\mod p
bamodp=a×inv(b)modp
其中
b
×
i
n
v
(
b
)
≡
1
(
m
o
d
p
)
b\times inv(b)\equiv1(\mod p)
b×inv(b)≡1(modp)
快速幂求解逆元
当p为质数时:
根据费马小定理,
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1}\equiv1(\mod p)
ap−1≡1(modp)
a
×
a
p
−
2
≡
1
(
m
o
d
p
)
a\times a^{p-2}\equiv 1 (\mod p)
a×ap−2≡1(modp)
a
p
−
2
a^{p-2}
ap−2即为逆元。使用快速幂可以快速求出。废话
时间复杂度O(logn)
代码实现:
#include<bits/stdc++.h>
using namespace std;
int mod,x;
inline int fp(int a,int b)
{
a%=mod;
int ans=1;
while(b)
{
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
int main()
{
scanf("%d%d",&x,&mod);
printf("%d\n",fp(x,mod-2)%mod);
return 0;
}
扩展欧几里得求逆元
根据定义
b
×
i
n
v
(
b
)
≡
1
(
m
o
d
p
)
b\times inv(b)\equiv1(\mod p)
b×inv(b)≡1(modp)
等价于
b
×
i
n
v
(
b
)
+
p
×
y
=
1
b\times inv(b)+p\times y =1
b×inv(b)+p×y=1
即对于
b
×
x
+
p
×
y
=
1
b\times x+p\times y =1
b×x+p×y=1
求解
x
x
x
根据上次讲的拓展欧几里得求即可。上一期传送门
时间复杂度O(logn),对于
p
p
p不是质数,也可以解。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int b,mod,x,y;
inline int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
return r;
}
int main()
{
scanf("%d%d",&b,&mod);
exgcd(b,mod,x,y);
while(x<0) x+=mod;
printf("%d",x);
}
线性递推求逆元
当
p
p
p是质数时,
递推过程:
设
t
=
p
/
i
,
k
=
p
m
o
d
i
t=p/i,k=p\mod i
t=p/i,k=pmodi
易得:
t
×
i
+
k
≡
0
(
m
o
d
p
)
t\times i+k\equiv0(\mod p)
t×i+k≡0(modp)
即
k
≡
−
t
×
i
(
m
o
d
p
)
k\equiv -t\times i(\mod p)
k≡−t×i(modp)
两边同除以
i
×
k
i\times k
i×k
i
n
v
(
i
)
≡
−
t
×
i
n
v
(
k
)
(
m
o
d
p
)
inv(i)\equiv-t\times inv(k)(\mod p)
inv(i)≡−t×inv(k)(modp)
带入
t
,
k
t,k
t,k
i
n
v
(
i
)
≡
−
p
/
i
×
i
n
v
(
p
m
o
d
i
)
(
m
o
d
p
)
inv(i)\equiv-p/i\times inv(p\mod i)(\mod p)
inv(i)≡−p/i×inv(pmodi)(modp)
为了避免负数,可以加
p
p
p且不影响结果,将
m
o
d
p
\mod p
modp提出最终得到:
i
n
v
(
i
)
≡
(
p
−
p
/
i
)
×
i
n
v
(
p
m
o
d
i
)
m
o
d
p
inv(i)\equiv(p-p/i)\times inv(p\mod i)\mod p
inv(i)≡(p−p/i)×inv(pmodi)modp
这样线性递推了。而且一次就求出了
1
n
1~n
1 n所有的逆元,所以适合大量的求逆元,而上面两种算法都是对单个求逆元,如果对n个求逆元,将会到达O(nlogn),而使用线性递推复杂度为O(n)。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define num ch-'0'
void get(int &res)
{
char ch;bool flag=0;
while(!isdigit(ch=getchar()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getchar());res=res*10+num);
(flag)&&(res=-res);
}
ll inv[200000005];
ll n,p;
int main()
{
scanf("%lld%lld",&n,&p);
inv[1]=1;
cout<<1<<endl;
for(ll i=2;i<=n;i++)
{
inv[i]=(p-p/i)*inv[p%i]%p;
cout<<inv[i]<<endl; //printf才能过
}
return 0;
}

浙公网安备 33010602011771号