乘法逆元详解

1. 什么是乘法逆元?

百度百科上是这样解释的:

乘法逆元,是指数学领域群 G 中任意一个元素 a ,都在 G 中有唯一的逆元 a',具有性质 a×a'=a'×a=e,其中 e 为该群的单位元。

不用看了,我知道你看不懂

逆元其实很简单,

\(a\cdot x\equiv1 \pmod {p}\),(一般 \(p\) 为质数)

则称 \(x\)\(a\)\(p\) 的逆元,记为 \(\operatorname{inv}(x)=a\)


2. 乘法逆元能干嘛?

逆元一般用来求形如 \(\frac{a}{b} \bmod p\) 的式子的值,

\(k\)\(b\)\(p\) 的逆元,则有 \(\frac{a}{b} \bmod p=ak\bmod p\)

下面来看看这个式子是怎么推导的。

\(k\)\(b\)\(p\) 的逆元,即 \(bk\bmod p=1\),有:

\[\begin{align*} \frac{a}{b}\bmod p &=\left(\frac{a}{b}\bmod p\right)\cdot 1\\ &=\left(\frac{a}{b}\bmod p\right)\left(bk\bmod p\right)\\ &=\left(\frac{a}{b}\cdot bk\right)\bmod p\\ &= ak\bmod p \end{align*} \]


3. 逆元怎么求?

(1) exgcd

还记得这个式子吗?\(ax\equiv 1 \pmod {p}\)

这个式子可以转化为 \(ax+py={1}\),(\(y\in\mathbb{Z}\)

这个时候就可以用 exgcd 来求逆元,

时间复杂度大约为 \(\mathcal O(\log n)\)

#include<bits/stdc++.h>
using namespace std;
void exgcd(int &x,int &y,int a,int b)
{
    if(!b)
    {
        x=1,y=0;
        return;
    }
    exgcd(x,y,b,a%b);
    int t=x;
    x=y;
    y=t-a/b*y;
}
int main()
{
    int n,p;//求n关于p的逆元
    scanf("%d %d",&n,&p);
    int x=0,y=0;
    exgcd(x,y,n,p);
    printf("%d",(x%p+p)%p);
    return 0;
}

(2) 费马小定理

费马小定理:若 \(p\) 为素数,\(a\in\mathbb{Z^+}\),且 \(a\)\(p\) 互质,则有 \(a^{p-1}\equiv 1(\bmod{p})\)

这时发现上式和式子 \(ax\equiv 1 \pmod {p}\) 的右边部分都为 \(1\)

所以,\(ax\equiv a^{p-1}\pmod p\)

化简得 \(x\equiv a^{p-2} \pmod p\)

如果 \(a^{p-2}\) 用快速幂计算,时间复杂度将和 exgcd 同级,为 \(\mathcal O(\log n)\)

code:

#include<bits/stdc++.h>
using namespace std;
int FastPow(int a,int n,int mod)
{
    int base=a,ans=1;
    while(n)
    {
        if(n&1) ans=(base*ans)%mod;
        base=(base*base)%mod;
        n>>=1;
    }
    return ans;
}
int main()
{
    int n,p;//求n关于p的逆元
    scanf("%d %d",&n,&p);
    printf("%d",FastPow(n,p-2,p));
    return 0;
}

(3) 逆元的线性递推式

此处参考 https://zhuanlan.zhihu.com/p/100587745?utm_source=qq

例如洛谷P3811,要求求区间 \([1,n]\) 的所有数的逆元,这个时候, exgcd 和费马小定理都显得有些“笨”。于是,就有了线性递推式。

假设我们现在想求 \(a\) 的逆元,为了方便之后的推导,我们将 \(p\) 表示成这样的形式: \(p=aq+r\)

此处,\(q=\lfloor p/a\rfloor,r=p\bmod a\)

容易看出,\(aq+r\equiv 0\pmod p\)

移项整理可得 \(a\equiv -r\cdot \operatorname{inv}(q)\pmod p\)

两边同时取倒数,得出 \(\operatorname{inv}(a)\equiv -q\cdot \operatorname{inv}(r)\pmod p\)

再将 \(q=\lfloor p/a\rfloor,r=p\bmod a\) 代入,我们就得到了逆元的递推式 \(\operatorname{inv}(a)\equiv -\lfloor p/a\rfloor\cdot \operatorname{inv}(p\bmod a)\pmod p\)

我们发现,式子中的 \(-\lfloor p/a\rfloor\) 有可能是负数,所以我们给它加一个 \(p\)

所以,最终的递推式为:

\[\operatorname{inv}(a)\equiv (p-\lfloor p/a\rfloor)\cdot \operatorname{inv}(p\bmod a)\pmod p \]

真正实现的时候,将 \(\equiv\) 当作 \(=\) 即可。

然后就可以实现 \(\mathcal O(n)\) 递推啦。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=10000010;
int n,p,inv[N];
int main()
{
    scanf("%d%d",&n,&p);
    inv[1]=1;
    for(int i=2;i<=n;i++)
        inv[i]=(p-p/i)*inv[p%i]%p;
    printf("%d",inv[n]);
    return 0;
}

4.参考资料

https://blog.csdn.net/yu121380/article/details/81188274

https://www.luogu.com.cn/blog/zjp-shadow/cheng-fa-ni-yuan

https://www.cnblogs.com/song-/p/9724555.html

https://zhuanlan.zhihu.com/p/100587745?utm_source=qq

posted @ 2019-12-15 21:01  zzt1208  阅读(2190)  评论(0编辑  收藏  举报