zoukankan      html  css  js  c++  java
  • 乘法逆元

    剩余系求逆元:

    对于某个(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;
    }
    
  • 相关阅读:
    ICMPv6 Type 和 rfc
    Redis学习
    Vue学习(一)
    《一线架构师实践指南》读后感(五)
    《一线架构师实践指南》读后感(四)
    Stream流
    泛型
    《架构漫谈》读后感
    《一线架构师实践指南》读后感(三)
    《一线架构师实践指南》读后感(二)
  • 原文地址:https://www.cnblogs.com/DReamLion/p/14442100.html
Copyright © 2011-2022 走看看