zoukankan      html  css  js  c++  java
  • 求逆元的四种算法(拓欧费马小线性推欧拉)

    求逆元的四种算法

    拓展欧几里得算法求逆元

    上一篇博客中已经讲过拓展欧几里得算法,并且讲解了求逆元的原理。这里只列出代码

    在要求逆元的数与p互质时使用

    代码

    //扩展欧几里得定理
    int ex_gcd(int a,int b,int& x,int& y)
    {
        if(b==0)
        {
            x=1;
            y=0;
            return a;
        }
        int ans = ex_gcd(b,a%b,x,y);
        int tmp = x;
        x = y;
        y = tmp-a/b*y;
        return ans;
    }
    int cal(int a,int m)
    {
        int x,y;
        int gcd = ex_gcd(a,m,x,y);
        //cout << "a " << a << " m " << m << " x " << x << " y " << y << endl;
        if(1%gcd!=0) return -1;
        x*=1/gcd;
        m = abs(m);
        int ans = x%m;
        if(ans<=0) ans += m;
        return ans;
    }
    

    费马小定理求逆元

    在p是素数的情况下,有(a^{p-1}equiv1(mod p)),即(a^{p-2}aequiv1(mod p))。所以a模p的逆元是(a^{p-2}),可用快速幂求解。

    代码

    //费马小定理
    long long q_pow(long long a,long long b,long long p)
    {
        long long res = 1;
        while(b)
        {
            if(b&1)
            {
                res = (res*a)%p;
            }
            a = (a*a)%p;
            b>>=1;
        }
        return res;
    }
    long long inverse(long long a,long long p)
    {
        return q_pow(a,p-2,p);
    }
    

    线性递推求逆元

    在p为质数且需要一次性打出[1,p-1]的所有逆元时可以使用

    公式推导:现在求k的逆元

    (ak+b=p)

    (b*inv[b]equiv1mod p)

    ((p-ak)*inv[b]equiv1mod p)

    ((p*inv[b]-ak*inv[b])equiv1mod p)

    因为(p*inv[b]equiv0mod p)

    (-ak*inv[b]equiv1mod p)

    (b=p\%k)

    (-ak*inv[p\%k]equiv1 mod p)

    (ak+b=p,所以a=p/k(整除))

    (-(p/k)*inv[p\%k]*kequiv1mod p)

    所以有(inv[k]=-(p/k)*inv[p\%k])

    使用的时候加上p去掉负号

    代码

    //线性递推
    int inv[max_n];
    void ksm(int p)
    {
        inv[1] = 1;
        for(int i = 2;i<=p-1;i++)
        {
            inv[i] = (p-p/i)*inv[p%i]%p;
        }
    }
    

    欧拉定理求逆元

    在p为非质数时使用

    欧拉定理表明,a,p互质时,有(a^{phi(p)}equiv1(mod p)),则a模p的逆元为(a^{phi(p)-1})。求出欧拉函数后可用快速幂求得逆元。

    代码

    原理是(phi(n)=n*prod_{i=1}^k(1-frac{1}{factor[i]}))。factor[i]表示n的因子

    //欧拉函数
    int phi(int x)
    {
        int ans = x;
        for(int i = 2;i*i<=x;i++)
        {
            if(x%i==0)
            {
                ans = ans/i*(i-1);
                while(x%i==0) x/=i;
            }
        }
        if(x>1)
            ans=ans/x*(x-1);
        return ans;
    }
    

    还有一种欧拉筛法,批量求欧拉函数,类似于埃氏筛

    代码

    //欧拉筛法
    int Phi[max_n];
    void euler(int N)
    {
        Phi[1] = 1;
        for(int i = 2;i<N;i++)
        {
            if(!Phi[i])
            {
                for(int j = i;j<N;j+=i)
                {
                    if(!Phi[j])
                    {
                        Phi[j] = j;
                    }
                    Phi[j] = Phi[j]/i*(i-1);
                }
            }
        }
    }
    

    另一种更高效的筛法

    原理:

    p为质数,(phi(p)=p-1)

    if(i\%p==0),then (phi(i*p)=phi(i)*p)

    if (i\%p!=0),then (phi(i*p)=phi(i)*(p-1)).

    代码

    //更快的欧拉筛
    int tot = 0;//质数个数
    int prime[max_n];//质数
    void Euler(int N)
    {
        Phi[1] = 1;
        for(int i = 2;i<N;i++)
        {
            if(!Phi[i])
            {
                Phi[i] = i-1;
                prime[tot++] = i;
            }
            for(int j = 0;j<tot&&(long long)i*prime[j]<N;j++)
            {
                if(i%prime[j])
                {
                    Phi[i*prime[j]] = Phi[i]*(prime[j]-1);
                }
                else
                {
                    Phi[i*prime[j]] = Phi[i]*prime[j];
                    break;
                }
            }
        }
    }
    

    还有一种似乎表面上原理不同,但实现与上面相似的欧拉筛,这次是直接求出[1,p-1]逆元的那种。

    逆元有个性质:

    (inv[a]*inv[b]=inv[a*b])

    因为(a*inv[a]equiv b*inv[b]equiv1(mod p)).

    (ab*inv[a]*inv[b]equiv1(mod p)),

    (inv[a*b]=inv[a]*inv[b]).

    所以对于每个合数 ,我们把所有它的因子的逆元筛出来再相乘即可。

    所以我们可以直接把所有素数筛出来,对它们求逆元(拓欧或费马小定理),再把它的逆元乘给它的倍数就可以了。

    //另一种欧拉筛
    int vis[max_n];
    int inv[max_n];
    int prime[max_n];//prime[0]记录质数个数
    void EUler(int p)
    {
        vis[1] = 1;
        inv[1] = 1;
        for(int i = 2;i<=p-1;i++)
        {
            if(!vis[i])
            {
                prime[++prime[0]] = i;
                inv[i] = q_pow(i,p-2,p);
            }
            for(int j=0;j<prime[0];j++)
            {
                if(i*prime[j]>p) break;
                inv[i*prime[j]]=inv[i]*inv[prime[j]];
                if(i%prime[j]) break;
            }
        }
    }
    

    写着写着老是有一种“回字的四种写法”的感觉ε=(´ο`*)))

    参考文章

    x义x的博客,【x义x讲坛】浅谈模质数意义下的乘法逆元,https://www.luogu.org/blog/zyxxs/post-xiao-yi-jiang-tan-qian-tan-sheng-fa-ni-yuan#

    镜外,ACM数论之旅7---欧拉函数的证明及代码实现(我会证明都是骗人的╮( ̄▽ ̄)╭),https://www.cnblogs.com/linyujun/p/5194170.html

    Rain722,乘法逆元的几种计算方法,https://blog.csdn.net/Rain722/article/details/53170288

    hehe_54321,乘法逆元(欧拉函数,欧拉定理,质数筛法),https://www.cnblogs.com/hehe54321/p/7778955.html

  • 相关阅读:
    streamsets 集成 cratedb 测试
    streamsets k8s 部署试用
    streamsets rest api 转换 graphql
    StreamSets sdc rpc 测试
    StreamSets 相关文章
    StreamSets 多线程 Pipelines
    StreamSets SDC RPC Pipelines说明
    StreamSets 管理 SDC Edge上的pipeline
    StreamSets 部署 Pipelines 到 SDC Edge
    StreamSets 设计Edge pipeline
  • 原文地址:https://www.cnblogs.com/zhanhonhao/p/11330792.html
Copyright © 2011-2022 走看看