算法笔记:乘法逆元

一,乘法逆元

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

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

群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\n",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

 

posted @ 2020-09-19 12:33  juruo-hxy  阅读(506)  评论(0编辑  收藏  举报