乘法逆元

剩余系求逆元:

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

求逆元:

费马小定理

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

\[a\times 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} \right \rfloor $ , $ j=p \ mod \ i $ ,有 $ p=ki+j $ ,因此得到:

\[ki+j \equiv 0 (mod \ p) \]

两边同时乘\(i^{-1} \times 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} \right \rfloor (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 = '\n')
{
    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} \times a_{n}\) 就与 \(a_{n}\) 的逆元抵消掉了,得到 \(a_{1}\)\(a_{n-1}\) 的积逆元 \(sv_{n-1}\)
所以就有了这个式子:

\[sv_{n-1}=sv_{n} \times a_{n} \]

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

\[a^{-1}_{i}=s_{i-1} \times 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\n",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 = '\n')
{
    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;
}
posted @ 2021-03-09 20:08  DReamLion  阅读(53)  评论(1编辑  收藏  举报