zoukankan      html  css  js  c++  java
  • 算法笔记:乘法逆元

    一,乘法逆元

    乘法逆元这个东西貌似高中数学都不会讲到

    你们可以看一下百度百科,里面的解释就是↓

    群G中任意一个元素a,都在G中有唯一的逆元a‘,具有性质aa'=a'a=e,其中e为群的单位元。

    但是这个解释貌似连我都看不懂……

    是我们只需要知道逆元就是一个x使a*x≡1(mod p)


    二,乘法逆元的性质

    性质1:存在唯一性

    我们假设a有相同的两个逆元a'和a''

    那么:

    a*a'≡a*a''≡1(mod p)

    所以不妨设a'>a'',并且a'-a''=k

    由于a≠0,所以k一定等于0(mod p),所以a'=a''

    与假设矛盾,所以每一个a只有对应的一个逆元a'

    性质2:完全积性函数

    为了接下来的表示方便我们把a的逆元表示为inv[a]

    这个性质就是说:inv[a]*inv[b]=inv[a*b]

    那么这个怎么证明呢???

    ∵a*inv[a]≡b*inv[b]≡1(mod p)

    ∴a*inv[a]*b*inv[b]≡1*1(mod p)

    ∴(a*b)*(inv[a]*inv[b])≡1(mod p)

    ∴inv[a]*inv[b]=inv[a*b]

    性质3:a*inv[b]≡a/b(mod p)

    ∵b*inv[b]≡1(mod p)

    ∴a*b*inv[b]≡a(mod p)

    ∴ a*inv[b]≡a/b(mod p)

    这个性质很重要!

    有的时候我们要求a/b mod p的值,我们只能在a上不断加上p,直到整除b

    如果a,b,p都很大那么这个算法就咕掉了

    所以这样我们只需要用逆元就能很快求出


     三,那么乘法逆元怎么求呢???

    方法1:枚举法(单个)

    枚举x使a*x≡1(mod p),怎么说呢???

    太蠢了叭……(出题人:给爷爪巴

    方法2:费马小定理(单个)

    由于费马小定理的存在↓(摘自百度百科)

    费马小定理(Fermat's little theorem)是数论中的一个重要定理,在1636年提出。如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p)。

    我们可以把a^(p-1)≡1(mod p)拆成a*a^(p-2)≡1(mod p)

    所以说a^(p-2)就是a mod p的乘法逆元

    然后快速幂求出a^(p-2)即可,代码就不展示了

    方法3:扩展欧几里得(单个)

    找到a*x≡1(mod p)相当于求出a*x+p*y=1的解

    然后于是我们再看到a,p一定互质,所以说我们就能用拓展欧几里得求出x和y

    于是x,就是a的逆元


    四,如何批量求逆元

    当我们看到上三种方法之后,我们发现:

    如果要批量求逆元,他们还会加一个O(n)的时间复杂度

    所以我们现在就要讨论那些可以批量解决逆元的方法

    方法4:欧拉筛(批量)

    这里就用到了完全积性函数,所以说要求这个数的逆元就等于他的所有质因子的逆元的乘积

    但如果是质数就需要用快速幂或者扩欧解决

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=25000528;
    int p,n;
    int vis[N],pri[N],inv[N];
    int ksm(int a,int b,int p)
    {
        int ans=1;
        while(b)
        {
            if(b&1) ans=ans*a%p;
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    int main()
    {
        cin>>n>>p;
        vis[1]=1,inv[1]=1;
        for(int i=2;i<=p-1;i++)
        {
            if(!vis[i]) pri[++pri[0]]=i,inv[i]=ksm(i,p-2,p);
            for(int j=1;j<=pri[0];j++)
            {
                if(i*pri[i]>=p) break;
                inv[i*pri[j]]=inv[i]*inv[pri[j]];
                if(i%pri[j]==0) break;
            }
        }
        for(int i=1;i<=n;i++) printf("%d
    ",inv[i]);
        return 0;
    }
    View Code

    方法5:线性递推(批量)

    现在我们要求k的逆元。

    首先1^-1≡1(mod p)

    然后我们设p=k*i+r

    再将这个式子放到mod p意义下就能得到

    k*i+r≡0(mod p)

    将两边同时乘上i^-1*r^-1就会得到

    k*r^-1+i^-1≡0(mod p)

    i^-1≡-k*r^-1(mod p)

    i^-i≡-(p/i)*(p%i)^-1(mod p)

    所以易得转移方程:

    inv[0]=inv[1]=1;  
    for(int i=2;i<N;i++)
        inv[i]=((mod-mod/i)*_inv[mod%i])%mod;

    于是全部的批量求逆元就讲完了

    然后,就不知道讲什么了

    看在码了这么多字,给个赞吧qwq

  • 相关阅读:
    RabbitMQ的ACK机制
    Flex保存文件 FileReference.save(data,filename)
    Flex Builder cannot locate the required debugger version of Flash Player
    Flex每日小记
    IT民工
    R读取文件内容到Frame
    ArcGIS9.2 9.3
    超时空的心情
    ArcMap中设置.mxd相对路径
    MyEclipse Flex Tomcat BlazeDS
  • 原文地址:https://www.cnblogs.com/juruo-hxy/p/13695467.html
Copyright © 2011-2022 走看看