剩余系求逆元:
对于某个(a),是否存在(b),使得(ab=1(mod m))
求逆元:
费马小定理
若(a)是一个整数,(p)是一个质数,则有
因此,当模数(p)为质数时,(a^{p-2}) 即为 (a) 的逆元
EXGCD
求(ax+my=1)的解
线性求逆元
求出(1,2,...,n)中每个数关于(p)的逆元
首先,(1^{-1}equiv 1(mod p))
其次对于递归情况(i^{-1}),我们令 $ k=left lfloor frac{p}{i}
ight
floor $ , $ j=p mod i $ ,有 $ p=ki+j $ ,因此得到:
两边同时乘(i^{-1} imes j^{-1}):
再代入(j=p mod i),有(p=ki+j),有:
我们注意到 (p mod i < i),而在迭代中我们完全可以假设所有的模(p)下的逆元(j^{-1}(j<i))是已知的
故我们就可以推出逆元,利用递归的形式,而使用迭代实现:
inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=(ll)(p-p/i)*inv[p%i]%p;
例题:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define ll long long
#define maxn 3000010
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
template<typename F>
inline void write(F x, char ed = '
')
{
static short st[30];short tp=0;
if(x<0) putchar('-'),x=-x;
do st[++tp]=x%10,x/=10; while(x);
while(tp) putchar('0'|st[tp--]);
putchar(ed);
}
ll n,p;
ll inv[maxn];
int main(){
read(n),read(p);
inv[1]=1;
for(int i=2;i<=n;i++){
inv[i]=(p-p/i)*inv[p%i]%p;//********
}
for(int i=1;i<=n;i++) write(inv[i]);
return 0;
}
线性求逆元2
如果需要求给定任意(n)个数(a_{i}(1 le a_{i} < p))在模(p)意义下的逆元,就需要这种方法(和上一种方法的区别在于(a_{i})是不连续的)
首先计算(n)个数的前缀积,记为(s_{i}),然后用快速幂或扩展欧几里得算出(s_{n})的逆元,记为(sv_{n})
因为 (sv_{n}) 是 (n) 个数的积的逆元,所以 (sv_{n} imes a_{n}) 就与 (a_{n}) 的逆元抵消掉了,得到 (a_{1}) 到 (a_{n-1}) 的积逆元 (sv_{n-1})
所以就有了这个式子:
同理我们可以算出所有(sv_{i}),于是得到
因此就能算出(n)个数的逆元了
时间复杂度(O(n+log p))
例题:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define ll long long
#define maxn 5000010
using namespace std;
template<typename T>
inline void read(T &x){
x=0;char c=getchar();bool flag=false;
while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
if(flag)x=-x;
}
ll n,p,k,a[maxn];
ll ans,tmp,s[maxn],inv[maxn];
ll qpow(ll x,ll y){
ll res=1;
for(;y;y>>=1,x=x*x%p) if(y&1) res=res*x%p;
return res%p;
}
int main(){
read(n),read(p),read(k);
for(int i=1;i<=n;i++) read(a[i]);
s[0]=1;tmp=k;
for(int i=1;i<=n;i++) s[i]=(s[i-1]*a[i])%p;
s[n]=qpow(s[n],p-2);
for(int i=n;i>=0;i--) inv[i]=(s[i]*s[i-1])%p,s[i-1]=(s[i]*a[i])%p;
for(int i=1;i<=n;i++){
ans=(ans+inv[i]*tmp)%p;
(tmp*=k)%=p;
}
printf("%lld
",ans);
return 0;
}
线性筛求逆元
因为逆元是完全积性函数,所以可以用线性筛
写法上大概就这样 就这
inv[1]=1;fc[1]=1;
for(int i=2;i<=n;i++){
if(fc[i]==0){
// fc[i]=i;
isprime[++cnt]=i;
inv[i]=qpow(i,p-2,p);
}
for(int j=1;j<=cnt&&i*isprime[j]<=n;j++){
fc[isprime[j]*i]=1;//notprime
inv[i*isprime[j]]=inv[i]*inv[isprime[j]]%p;
if(i%isprime[j]==0) break;
}
}
upd:线性筛没问题的完全能A……之前的是写错了……之前写的是个鬼啊
例题:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define ll long long
#define maxn 30000010
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
template<typename F>
inline void write(F x, char ed = '
')
{
static short st[30];short tp=0;
if(x<0) putchar('-'),x=-x;
do st[++tp]=x%10,x/=10; while(x);
while(tp) putchar('0'|st[tp--]);
putchar(ed);
}
ll n,p,cnt=0,inv[maxn],fc[maxn],isprime[maxn];
ll qpow(ll a,ll b,ll p){
ll res=1;
for(;b;b>>=1,a=a*a%p) if(b&1) res=res*a%p;
return res%p;
}
int main(){
read(n),read(p);
inv[1]=1;fc[1]=1;
for(int i=2;i<=n;i++){
if(fc[i]==0){
// fc[i]=i;
isprime[++cnt]=i;
inv[i]=qpow(i,p-2,p);
}
for(int j=1;j<=cnt&&i*isprime[j]<=n;j++){
fc[isprime[j]*i]=1;//notprime
inv[i*isprime[j]]=inv[i]*inv[isprime[j]]%p;
if(i%isprime[j]==0) break;
}
}
for(int i=1;i<=n;i++) write(inv[i]);
return 0;
}