一.逆元的定义
若ab
≡
\equiv
≡ 1 mod p,则称b为a模p意义下的逆元。
逆元可以看作是一个分子为一的分数。
二.逆元的应用
逆元主要用于用于求组合数。
三.求逆元的方法
1.费马小定理求逆元
根据费马小定理a^(p-1)%p=1%p。
等式两边同除以a得,a^(p-2)%p=(1/a)%p。
得a在模p意义下的逆元就等于a^(p-2)%p,用快速幂,可在log2§的复杂度中求出。
#include<bits/stdc++.h>
using namespace std;
int qsm(int x,int y,int mod){//快速幂
int res=1,k=x;
while(y){
if(y&1) res=(res*k)%mod;
k=(k*k)%mod;
y>>=1;
}
return res%mod;
}
int main(){
int n,mod;
scanf("%d%d",&n,&mod);
for(int i=1;i<=n;i++){
ans=qsm(i,mod-2,mod);
printf("%d在模mod意义下的逆元为%d\n",i,ans);
}
return 0;
}
时间复杂度为O(n*log2(mod))
2.预处理求逆元
推导过程如下:
代码:
#include<bits/stdc++.h>//求1~n在模p意义下的逆元
using namespace std;
const int N=3e6+10;
int n;
long long p,inv[N];
int main(){
scanf("%d%d",&n,&p);
inv[1]=1;//1在模p意义下的逆元为1
for(int i=1;i<=n;i++){
if(i!=1) inv[i]=(p-(p/i))*inv[p%i]%p;//p%i一定小于i,则inv[p%i]已在之前计算过
printf("%d\n",inv[i]);
}
return 0;
}
时间复杂度为O(n)
3.通过求阶乘的逆元求每个数的逆元
过程为:
1.递推求出1~n的阶乘数组fac[]。
2.用费马小定理求出fac[n]的逆元g[n]。
3.根据g[i]=g[i+1]*(i+1) 递推出每个g[i]。
4.则数x的逆元为g[x]*fac[x-1]。
这种算法复杂度也为O(n),但相较于第二种算法的优势是处理了阶乘数组,和阶乘逆元数组。
在计算组合数时可直接调用。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int T,n,m;
long long fac[N],g[N],h[N];
int q_pow(int x,int y,int m){
long long res=1,k=x;
while(y){
if(y&1) res=(res*k)%m;
k=(k*k)%m;
y>>=1;
}
return res%m;
}
void Get_inv(){
fac[0]=1;
for(int i=1;i<N;i++)
fac[i]=(fac[i-1]*i)%m;
g[N-1]=q_pow(fac[N-1],m-2,m);
for(int i=N-2;i>=0;i--){
g[i]=(g[i+1]*(i+1))%m;
h[i]=(g[i]*fac[i-1])%m;
}
}
int main(){
scanf("%d%d",&n,&m);
Get_inv();
for(int i=1;i<=n;i++){
printf("%lld\n",h[i]);
}
return 0;
}
时间复杂度为O(n)