逆元

定义

在取模意义下的运算中,加减乘法都可以进行 $$ax%p=a%p imes x%p $$ 这样的结合操作,然而除法不能这么做。那么对于取模意义下的:

[axequiv1quad(mod;p) ]

我们就称 (x)(a)(p) 意义下的逆元,即 (inv(a))(p) 为质数)。

这样就把除法转换成了乘法操作。

  • 逆元非常重要,很多 (OI) 数学题都建立在逆元基础上。

逆元的求法

费马小定理

这个方法只有在 (p) 是质数时能用。(费马小定理成立条件)

没学费马小定理的可以在本人 (Blog) 查看。

由费马小定理可知:

[egin{align} a^{p-1}equiv 1quad(mod;p) ag1\ axequiv 1quad(mod;p) ag 2 end{align}\ herefore axequiv a^{p-1}quad(mod;p)\ herefore xequiv a^{p-2}quad(mod;p) ]

(x) 即为 (a) 的逆元,所以只需要用快速幂求即可。

(frak{Code})

int ksm(int x,int q=mo-2,int mo){
	int ret=1;
	while(q>0){
		if(q&1) ret=(1ll*ret*x)%mo;
		x=(1ll*x*x)%mo;
		q>>=1;
	}
	return ret%mo;
}

exgcd

学习过 (exgcd) 后,不难发现其实逆元就是在解线性同余方程。

那么只要解:(ax+py=1) 就行了。

(frak{Code})

void exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
	}
	else{
		exgcd(b,a%b,y,x);
		y-=(a/b)*x;
	}
}
int main()
{
	scanf("%d %d",&a,&p);
	int x,y;
	exgcd(a,p,x,y);
	inv=((x%p)+p)%p;
	printf("%d",inv);
	return 0;
}

线性推逆元

  1. (1)(n) 的逆元

此方法只有在 (p) 是质数时能用。(逆元只有在 (i)(p) 互质时有意义,如此大范围的求逆元,只有质数能行)

推柿子:

[p=i imeslfloor frac{p}{i} floor+p\%i implies i imeslfloor frac{p}{i} floor+p\%iequiv 0quad(mod;p)\ implies p\%iequiv-i imeslfloor frac{p}{i} floor\ 两边同乘 inv(i) imes inv(p\%i)\ implies inv(i)equiv -lfloor frac{p}{i} floor imes inv(p\%i) ]

这样就可以递推求逆元辽。

(Code)

#include<cstdio>
#include<algorithm>
using namespace std;
typedef int int_;
#define int long long

int inv[3000050];
int n,p;

int mod(int o,int mm){
	return ((o%mm)+mm)%mm;
}

int_ main()
{
	scanf("%lld %lld",&n,&p);
	inv[1]=1;
	printf("%d
",1);
	for(int i=2;i<=n;i++){
		inv[i]=(-p/i)*inv[p%i];
		inv[i]=mod(inv[i],p);
		printf("%lld
",inv[i]);
	}
	return 0;
 } 

MOD: luogu P3811

需要线性推,但是也可以练着写写上面两种求法((80 points))。

  1. (1!)(n!) 的逆元

在打组合数时经常用到,比上面那个简单一些,由于 (inv((n-1)!)=inv(n!) imes n) ,只要求出最大的阶乘的逆元,然后倒着退回来就行辽。

  1. 求数列 (a_1)(a_n) 的逆元

想法和 (2.) 很像,我们记录一个前缀积数组 (fac),然后求所有数的积的逆元 (inv)。之后从 (n) 向前递推,先求出 (inv(i) = fac(i-1) imes inv) ,再将 $inv $ 乘上 (a_i) 变成前 (i-1) 位的积的逆元。如此重复到 (1) 就行辽。

MOD: luogu P5431

这题卡常,各位粘个读优吧。

(Code)

#include<cstdio>
#include<algorithm>
using namespace std;

int n,p,k,inv;
int kfac[5000005],fac[5000005],s[5000005];
int ans;

int read() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}

void exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
	}
	else{
		exgcd(b,a%b,y,x);
		y-=(a/b)*x;
	}
}



int main()
{
	scanf("%d %d %d",&n,&p,&k);
	fac[0]=1;
	kfac[0]=1;
	for(int i=1;i<=n;i++){
		s[i]=read();
		fac[i]=(1ll*fac[i-1]*s[i])%p;
	    kfac[i]=(1ll*kfac[i-1]*k)%p;	
	}
	int x,y;
	exgcd(fac[n],p,x,y);
	inv=((x%p)+p)%p;
	for(int i=n;i>=1;i--){
		ans+=(1ll*kfac[i]*((1ll*inv*fac[i-1])%p)%p);
		if(ans>=p) ans-=p;
		inv=(1ll*inv*s[i])%p;
	}
	printf("%d",ans);
	return 0;
}

[luogu P4783] 矩阵求逆

不知到矩阵的逆的可以去学习一下线性代数

这道题体现了逆元的另一个用法,防止出现精度问题。由于逆元把除法变成乘法,完全就不会出现精度。

具体要用到 (Gauss-Jordan) 消元,在上面那个链接的 (P3) 后半段。

(Code)

#include<cstdio>
#include<algorithm>
using namespace std;
#define MOD 1000000007

int n;

int ksm(int x,int q,int mo){
	int ret=1;
	while(q>0){
		if(q&1) ret=(1ll*ret*x)%mo;
		x=(1ll*x*x)%mo;
		q>>=1;
	}
	return ret%mo;
}

int mod(int o,int mo){
	return ((o%mo)+mo)%mo;
}

struct MAT{
	int m[401][401];
	int nn;
	MAT(int x){
		nn=x;
		for(int i=1;i<=nn;i++){
			for(int j=1;j<=nn;j++){
				m[i][j]=0;
			}
		}
	}
	void input(){
		for(int i=1;i<=nn;i++){
			for(int j=1;j<=nn;j++){
				scanf("%d",&m[i][j]);
			}
		}
	} 
	void init(){
		for(int i=1;i<=nn;i++){
			for(int j=1;j<=nn;j++){
				m[i][j]=0;
				if(i==j) m[i][j]=1;
			}
		}
	}
	void mul(int x,int q){
		for(int i=1;i<=nn;i++){
			m[x][i]=(1ll*m[x][i]*q)%MOD;
		}
	}
	void elm(int x,int y,int q){
		for(int i=1;i<=nn;i++){
			m[x][i]-=(1ll*q*m[y][i])%MOD;
			m[x][i]=mod(m[x][i],MOD);
		}
	}
	void output(){
		for(int i=1;i<=nn;i++){
			for(int j=1;j<=nn;j++){
				printf("%d ",m[i][j]);
			}
			printf("
");
		}
	}
};


int main()
{
	scanf("%d",&n);
	MAT A(n),B(n);
	A.input();B.init();
	for(int i=1;i<=n;i++){
		if(A.m[i][i] == 0){
			printf("No Solution");
			return 0;
		}
		int inv=ksm(A.m[i][i],MOD-2,MOD);
		A.mul(i,inv);B.mul(i,inv);
		for(int j=1;j<i;j++){
			int tp=A.m[j][i];
			A.elm(j,i,tp);
			B.elm(j,i,tp);
		}
		for(int j=i+1;j<=n;j++){
			int tp=A.m[j][i];
			A.elm(j,i,tp);
			B.elm(j,i,tp);
		}
	}
	B.output();
	return 0;
}


-EOF-
原文地址:https://www.cnblogs.com/thornblog/p/11889737.html