浅谈乘法逆元

做题做得有些累了,过来写一篇总结


乘法逆元式长什么样:$$ax\equiv1(\mod p)$$

问题:求解\(x\)的值


看了网上的题解,有两种比较容易理解的方法:


  1. 扩展欧几里得解法,时间复杂度\(O(\ln n)\)(非常快)

  2. 递推求逆元,时间复杂度\(O(n)\)

    明显第一种方法是用于求单个逆元的,第二种方法是用于求\(N\)不然我写它干嘛,费马/欧拉函数\(+\)快速幂\(O(log_{10}N)\)不更快吗

解法1:扩展欧几里得

首先我们知道,扩展欧几里得的式子长这样:$$ax+by=c(\gcd (a,b))$$

那么怎么去找扩欧逆元的关系呢?

逆元:$$ax\equiv1(\mod p)$$

我们发现,这个式子可以转化成这样:$$ax-py=1$$

长得有点儿像

所以gcd(a,p)为1时,该式子才有解

然后把\(-\)改成\(+\)就可以了 p也不重要,直接取反就行

At Last

\[ax+py=1 \]

简直一模一样

然后套一个扩欧就行了

#include<cstdio>
#include<iostream>
using namespace std;
const int mod=39916801;
int exgcd(int a, int b, int &x, int &y) {   //扩欧
    if(b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int r = exgcd(b, a % b, x, y);
    int t = x;
    x = y;
    y = t - a / b * y;
    return r;
}
int main(){
    int a, p, x, y;
    scanf("%d%d", &a, &p);     //原式中的a,p
    int tot = exgcd(a,p,x,y);
    if(tot > 1){               //根据扩欧性质,a,p必须互质
        puts("No Answer!");
        return 0;
    }
    else{
        printf("%d", (x % mod + mod) % mod);
        return 0;
    }
}


解法2:递推求逆元 \(\text重点**\)

原理

设p是模数,i是待求的逆元,我们求的是i−1

在mod p意义下的值

\[p=k \times i+r \]

令 r < i,则k=p/i,r=p%i

\[k \times i+r≡0(\mod p) \]

\[k \times r^{-1}+i^{-1}≡0(\mod p) \]

\[i^{-1}≡−k \times r^{-1}(\mod p) \]

\[i^{-1}≡−p\div i\times inv[p\mod i] \]

嗯。。好难看的公式
说白了就是:$$inv[i]=-(mod/i)*inv[i%mod]$$
然后边界是$$inv[1]=1$$
这不仅为我们提供了一个线性求逆元的方法,也提供了一种\(O(log_2\mod)\)求逆元的方法

代码

线性求逆元

LL inv[mod+5];
void getInv(LL mod){
    inv[1]=1;
    for(int i=2;i<mod;i++)
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}

注意:

1.调用前要先预处理
2.调用的时候要先对除数取mod

性能分析:

时间复杂度O(n)
适用范围:mod数是不大的素数而且多次调用,比如卢卡斯定理。

递归求逆元

LL inv(LL i){
    if(i==1)return 1;
    return (mod-mod/i)*inv(mod%i)%mod;
}

性能分析

1.时间复杂度:O(log mod)
2.好像找到了最简单的算法了!!
3.适用范围: mod数是素数,所以并不好用,比如中国剩余定理中就不好使,因为很多时候可能会忘记考虑mod数是不是素数。

posted @ 2022-08-22 21:02  Tonvia  阅读(25)  评论(0)    收藏  举报
#Snow{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 99999; background: rgba(125,137,95,0.1); pointer-events: none; }