乘法逆元的几种求法总结

乘法逆元

对于缩系中的元素,每个数a均有唯一的与之对应的乘法逆元x,使得ax≡1(mod n) 
一个数有逆元的充分必要条件是gcd(a,n)=1,此时逆元唯一存在 
逆元的含义:模n意义下,1个数a如果有逆元x,那么除以a相当于乘以x。

下面给出求逆元的几种方法

1 循环找解法

给定模m和需要求逆的数x,直接暴力枚举1~m-1 
检查是否有x*i=1(mod m)

这种算法可以应用与写暴力、对拍、模数较小,求逆次数少的情况 
时间复杂度O(m)

2 扩展欧几里得算法

给定模数m,求a的逆相当于求解ax=1(mod m) 
这个方程可以转化为ax-my=1 
然后套用求二元一次方程的方法,用扩展欧几里得算法求得一组x0,y0和gcd 
检查gcd是否为1 
gcd不为1则说明逆元不存在 
若为1,则调整x0到0~m-1的范围中即可

void ext_gcd(int a,int b,int &d,int &x,int &y){
    if (b==0){
        d=a;x=1;y=0;
        return;
    }
    ext_gcd(b,a%b,d,y,x);//回代
    y-=a/b*x;
}
int main(){
    scanf("%d%d",&m,&n);//m关于n的逆元
    ext_gcd(m,n,d,x,y);
    printf("%d\n",(x+n)%n);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这种算法效率较高,常数较小,时间复杂度为O(ln n)

3 费马小定理及欧拉定理

在模为素数p的情况下,有费马小定理 
a^(p-1)=1(mod p) 
那么a^(p-2)=a^-1(mod p) 
也就是说a的逆元为a^(p-2)

而在模不为素数p的情况下,有欧拉定理 
a^phi(m)=1(mod m) (a⊥m) 
同理a^-1=a^(phi(m)-1)

因此逆元x便可以套用快速幂求得了x=a^(phi(m)-1)

但是似乎还有个问题?如何判断a是否有逆元呢? 
再求一次gcd判断是否互质吗? 
这还不如直接用扩展欧几里得算法呢>_<

其实问题很简单,直接判断这是不是逆元就行了: 
检验逆元的性质,看求出的幂值x与a相乘是否为1即可

这种算法复杂度为O(log2 n) 
在几次测试中,常数似乎较上种方法大

4 O(n)求1~n逆元表

有时会遇到这样一种问题, 
在模质数p下,求1~n逆元 n< p

这个问题有种很牛的算法,其基于以下的推导: 
在求i的逆元时 
p%i+[p/i]*i=p 
令a=p%i,b=[p/i],则有 
a+b*i=p 
a+b*i=0(mod p) 
b*i=-a(mod p) 
i^-1=-b/a 
也就是说i的逆元为:-[p/i]*(p%i)^-1 
而p%i<i,那么可以从2递推到n求逆元,在求i之前p%i一定已经求出 
这样就可以O(n)求出所有逆元了 
(初始化 1^(-1)=1) 
代码如下

    inv[1]=1;
    fo(i,2,n)
        inv[i]=(long long)(p-p/i)*inv[p%i]%p;
  • 1
  • 2
  • 3

除了上面这种算法之外,还有几种O(n)的逆元表求法,不过没有上面这种方法简便。 
A 可以先O(n)预处理出1!~n! 然后用O(log n)的复杂度求出n!的逆元 
然后就可以O(n)处理出i!的逆元 (1/i!=(i+1)/(i+1)! 倒推即可) 
接下来就可以O(n)求出所有i的逆元 1/i=(i-1)!/i! 
需要同时预处理阶乘 阶乘的逆元的题目可以采用这种方法求逆元表。

这种方法扩展性比较强,可以用这种方法求出一个序列中所有元素(元素前缀积)的逆元,复杂度也是O(n),比一个个单独计算逆元效率更优。

B 可以用线性筛法。由于逆元是积性函数,合数的逆元可以O(1)计算,质数的逆元可以用O(log n)的复杂度求。由于n以内质数大约有n/log n个。总复杂度也是O(n)

调试问题

最后再简单讲讲如何调试,检测求得的逆元是否正确 
还是同上面所说的那样,直接测试其与原数相乘是否为1即可了

这里顺便再啰嗦一句:如何检验快速幂算法是否正确呢(这还用检验
随便找1个质数p,检测a^(p-1)%p是否都为1就行啦 (1<=a<p)

你可能感兴趣的:(数论)