首先,通过下面的式子来看看什么是乘法逆元~
x * n % P = 1,其中x和P为已知且互素,n未知(比如在 2 * n % 7 = 1 这个式子里,n就是乘法逆元)
弄懂什么是乘法逆元,来看看有什么姿势可以把它求出来吧~
姿势1.暴力(此姿势不要求P为素数)
没有什么问题是一个暴力解决不了的,如果有,那就两个(手动滑稽)
代码如下:
int n;
for(int i = 1 ;i <= P ;i++)
{
if(x * i % P == 1)
{
n = i;
break;
}
}
虽然暴力简单粗暴,但是它的时间复杂度为O(P),只能在P较小的情况使用,局限性有点大。
姿势2.快速幂(此姿势要求P为素数)
这个姿势是由费马巨巨的费马小定理(x^(P-1) ≡ 1 mod P)得来的:
x^(P-1) ≡ 1 mod P 即 x^(P-1) % P = 1
已知 x * n % P = 1
所以有 x * n % P = x^(P-1) % P 即 n % P = x^(P-2) % P
到这里可以看出,逆元有无数个:n,n+P,n+2*P,…
一般情况下我们只要求那个最小的正整数,这样问题就转化为求 x^(P-2) % P
代码就不贴了,在网上找个有求模快速幂的模版,把x,P-2,P代进去就可以了
这个姿势的时间复杂度为O(log P),是个不错的姿势(手动滑稽)
姿势3.扩展欧几里德算法(此姿势不要求P为素数)
首先,我们将式子变形一下~
x * n % P = 1 等价于 n * x + k * P = 1,其中k为整数
因为 x 和 P 互素,所以 gcd (x ,P) = 1(最大公约数为1)
所以有 n * x + k * P = gcd (x ,P)
这样问题就转化为,求二元一次方程 n * x + k * P = gcd (x ,P) 的解
代码同样不贴了, 在网上找个扩展欧几里德的模版,把 x , P 代进去。
不过要注意,算出来的n有可能是负数,也有可能不是最小的。若需要得到最小的正整数,加上如下代码即可:
n = n % P;//处理不是最小的情况
while(n < 0)//处理负数的情况
{
n =( n + P ) % P;
}
到这里可以得出结论:逆元的集合为{…,n - 2 * P,n - P,n , n + P,n + 2 * P,… }
这个姿势的时间复杂度为O(log P),是个不错的姿势(手动滑稽)