学习笔记:求逆元的三种方法

前言

对于模运算来说:
( 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 (ab)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) ap11(modp)
a × a p − 2 ≡ 1 ( m o d    p ) a\times a^{p-2}\equiv 1 (\mod p) a×ap21(modp)
a p − 2 a^{p-2} ap2即为逆元。使用快速幂可以快速求出。废话
时间复杂度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+k0(modp)
k ≡ − t × i ( m o d    p ) k\equiv -t\times i(\mod p) kt×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)(pp/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;
}

おすすめ

転載: blog.csdn.net/pigonered/article/details/121183920