逆元

定义

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

\[ax\equiv1\quad(mod\;p) \]

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

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

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

逆元的求法

费马小定理

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

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

由费马小定理可知:

\[\begin{align} a^{p-1}\equiv 1\quad(mod\;p)\tag1\\ ax\equiv 1\quad(mod\;p) \tag 2 \end{align}\\ \therefore ax\equiv a^{p-1}\quad(mod\;p)\\ \therefore x\equiv 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\times\lfloor \frac{p}{i}\rfloor+p\%i \implies i\times\lfloor \frac{p}{i}\rfloor+p\%i\equiv 0\quad(mod\;p)\\ \implies p\%i\equiv-i\times\lfloor \frac{p}{i}\rfloor\\ 两边同乘\ inv(i)\times\ inv(p\%i)\\ \implies inv(i)\equiv -\lfloor \frac{p}{i}\rfloor\times 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\n",1);
	for(int i=2;i<=n;i++){
		inv[i]=(-p/i)*inv[p%i];
		inv[i]=mod(inv[i],p);
		printf("%lld\n",inv[i]);
	}
	return 0;
 } 

MOD: luogu P3811

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

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

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

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

想法和 \(2.\) 很像,我们记录一个前缀积数组 \(fac\),然后求所有数的积的逆元 \(inv\)。之后从 \(n\) 向前递推,先求出 \(inv(i) = fac(i-1) \times 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("\n");
		}
	}
};


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-
posted @ 2019-11-19 15:18  T_horn  阅读(242)  评论(0编辑  收藏  举报