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

  • 相关阅读:
    Java常量初始化后不会再去重新获取
    Intellij IDEA自动编译问题
    Tomcat关闭日志输出
    MySQL命令行导出数据库
    补充Mysql5.7用法
    Linux下安装 mysql 5.7
    IE9 表格错位bug
    Ubuntu忘记管理员密码
    实体转换计算器
    js生成二维码参数设置
  • 原文地址:https://www.cnblogs.com/zhanhonhao/p/11330792.html
Copyright © 2011-2022 走看看