浅谈乘法逆元
做题做得有些累了,过来写一篇总结
乘法逆元式长什么样:$$ax\equiv1(\mod p)$$
问题:求解\(x\)的值
看了网上的题解,有两种比较容易理解的方法:
-
扩展欧几里得解法,时间复杂度\(O(\ln n)\)(非常快)
-
递推求逆元,时间复杂度\(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数是不是素数。

浙公网安备 33010602011771号