对于一个数 x 和一个模数 p,若存在一个数字 y,满足
则称 y 是 x 在模 p 意义下的逆元,记做
。
一个数字逆元在模意义下的运算中可以完全取代该数字的倒数。例如
,其中
代表 y的逆元。
单个数求逆元有扩展欧几里得、费马小定理 复杂度logn
对于竞赛中,一般常用的是费马小定理,因为模数一般是质数
具体如下:
根据欧拉定理
其中
为欧拉函数,
表示小于 p 的正整数中与 p 互质的数的个数。
等式两侧同乘
可以得到
显然当 p 是一个质数时,
,这时可以 O(1) 算出
的值,即可用快速幂 求出 x的逆元。这个算法好写好记,常数也较小。一般当 p 为 int
范围内的质数时选择此算法。当 p 不在 int
范围内时,由于快速幂时需要两个 long long
相乘,会爆精度。
至于证明,自行百度,会用就行~
线性递推阶乘求逆元结论:
其中p为模数,inv[i]表示数i的逆元
对了,欧拉定理有个好用的结论,需要记下来:
通过这个式子可以有效的简化模数
最一般的,给你n个数:
我们设数组a的各项是:
则其逆元可表示为:
则
其中pre[i]表示前i个数相乘,suf[i]表示从第n个数开始倒回去乘到第i个数,(模p意义下)
这样o(n)我们就可以求出这些数的逆元了~
附上两题模板题:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rg register ll
#define inf 2147483647
#define lb(x) (x&(-x))
ll sz[200005],n;
template <typename T> inline void read(T& x)
{
x=0;char ch=getchar();ll f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}x*=f;
}
inline ll query(ll x){ll res=0;while(x){res+=sz[x];x-=lb(x);}return res;}
inline void add(ll x,ll val){while(x<=n){sz[x]+=val;x+=lb(x);}}//第x个加上val
ll p,inv[5000005]={0,1};
int main()
{
cin>>n>>p;
cout<<1<<endl;
for(rg i=2;i<=n;i++)
{
inv[i]=p-(p/i)*inv[p%i]%p;
printf("%lld\n",inv[i]);
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rg register ll
#define inf 2147483647
#define lb(x) (x&(-x))
template <typename T> inline void read(T& x)
{
x=0;char ch=getchar();ll f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}x*=f;
}
ll a[5000005],sum,suf[5000005],n,p,k,pre;
inline ll qp(ll x,ll m)
{
ll res=1;
while(m)
{
if(m&1)res=res*x%p;
x=x*x%p;
m>>=1;
}
return res%p;
}
inline ll in(ll x)
{
return qp(x,p-2);
}
int main()
{
cin>>n>>p>>k;
ll k1=k;
pre=1,suf[n+1]=1;
for(rg i=1;i<=n;i++)read(a[i]);
for(rg i=n;i>=1;i--)suf[i]=suf[i+1]*a[i]%p;
ll zong=in(suf[1]);
for(rg i=1;i<=n;i++)sum+=zong%p*pre%p*suf[i+1]%p*k1,sum%=p,k1=k1*k%p,pre=pre*a[i]%p;
cout<<sum<<endl;
return 0;
}