逆元
定义
在取模意义下的运算中,加减乘法都可以进行 $$ax%p=a%p \times x%p $$ 这样的结合操作,然而除法不能这么做。那么对于取模意义下的:
我们就称 \(x\) 为 \(a\) 模 \(p\) 意义下的逆元,即 \(inv(a)\)( \(p\) 为质数)。
这样就把除法转换成了乘法操作。
- 逆元非常重要,很多 \(OI\) 数学题都建立在逆元基础上。
逆元的求法
费马小定理
这个方法只有在 \(p\) 是质数时能用。(费马小定理成立条件)
没学费马小定理的可以在本人 \(Blog\) 查看。
由费马小定理可知:
\(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\) 到 \(n\) 的逆元
此方法只有在 \(p\) 是质数时能用。(逆元只有在 \(i\) 和 \(p\) 互质时有意义,如此大范围的求逆元,只有质数能行)
推柿子:
这样就可以递推求逆元辽。
\(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!\) 到 \(n!\) 的逆元
在打组合数时经常用到,比上面那个简单一些,由于 \(inv((n-1)!)=inv(n!)\times n\) ,只要求出最大的阶乘的逆元,然后倒着退回来就行辽。
- 求数列 \(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;
}