乘法逆元
对于缩系中的元素,每个数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;
}
这种算法效率较高,常数较小,时间复杂度为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;
除了上面这种算法之外,还有几种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)