乘法逆元 费马小定理 EXGCD 线性求逆元 线性求逆元2 线性筛求逆元

剩余系求逆元:

对于某个(a),是否存在(b),使得(ab=1(mod m))

求逆元:

(a)是一个整数,(p)是一个质数,则有

[a imes a^{p-2} equiv 1 (mod 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 $ ,因此得到:

[ki+j equiv 0 (mod p) ]

两边同时乘(i^{-1} imes j^{-1}):

[kj^{-1}+i^{-1} equiv 0 (mod p) ]

[i^{-1} equiv -kj^{-1} (mod p) ]

再代入(j=p mod i),有(p=ki+j),有:

[i^{-1} equiv -left lfloor frac{p}{i} ight floor (p mod i)^{-1} (mod p) ]

我们注意到 (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;

例题:

Luogu P3811 乘法逆元

#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_{n-1}=sv_{n} imes a_{n} ]

同理我们可以算出所有(sv_{i}),于是得到

[a^{-1}_{i}=s_{i-1} imes sv_{i} ]

因此就能算出(n)个数的逆元了
时间复杂度(O(n+log p))

例题:

Luogu P5431 乘法逆元2

#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……之前的是写错了……之前写的是个鬼啊

例题:

Luogu P3811 乘法逆元

#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;
}