逆元的几种求法
我们知道模运算有如下的运算规则:
- (a+b)%p=(a%p+b%p)%p
- (a-b)%p=(a%p-b%p)%p
- (a*b)%p=(a%p*b%p)%p
模运算与基本四则运算有些相似,但是除法例外。
既然除一个数不满足取模,我们就改成让他乘一个数,使得乘之后取模的结果与原答案相同。乘的这个数就是乘法逆元。
乘法逆元的定义:
对于正整数a和p(假设a存在乘法逆元,即a和p互质),如果有ax≡1(mod p),那么把x的最小正整数解叫做a模m的乘法逆元。
知道了逆元的作用,我们来介绍几种逆元的求法。(以模数为1e9+7为例)
1.费马小定理:
费马小定理(Fermat's little theorem)是数论中的一个重要定理,在1636年提出,其内容为: 假如p是质数,且gcd(a,p)=1,那么 a(p-1)≡1(mod p)。(摘自百度百科)
应用费马小定理可求得a模p的乘法逆元,inv(a)=a(p-2)modp。(p为质数)
推导:
a(p-1)≡1(mod p) => a· a(p-2) ≡1 (mod p) => a(p-2)≡a-1 (mod p)
有了费马小定理用快速幂就能求得a模p的逆元,时间复杂度为O(log N)
1 const int mod=1e9+7; 2 typedef long long LL; 3 4 LL Fast_pow(LL a,int b){ 5 LL ans=1; 6 while(b){ 7 if(b&1) 8 ans=ans*a%mod; 9 a=a*a%mod; 10 b>>=1; 11 } 12 return ans; 13 } 14 15 LL inv(LL a,int p){ 16 return Fast_pow(a,p-2); 17 }
2.扩展欧几里得:
扩展欧几里德算法是用来在已知a, b求解一组x,y,使它们满足贝祖等式: ax+by = gcd(a, b) =d(解一定存在,根据数论中的相关定理)。(摘自百度百科)
根据逆元的定义,求a模p的逆元就是求解ax≡1(mod p)的最小正整数解,也就是相当于求解不定方程ax-py=1 (a和p互质) 。对于不定方程的求解,我们可以使用扩展欧几里得。
时间复杂度为O(log N)
1 const int mod=1e9+7; 2 typedef long long LL; 3 4 int exEuclid(int a,int b,int & x,int &y){ 5 if(!b){ 6 x=1; 7 y=0; 8 return a; 9 } 10 int r=exEuclid(b,a%b,x,y); 11 int t=x; 12 x=y; 13 y=t-a/b*y; 14 return r; 15 } 16 17 LL inv(LL a){ 18 LL x,y; 19 int temp=exEuclid(a,mod,x,y); 20 return temp==1?(x+mod)%mod:-1; //若gcd(a,p)!=1 (即a和p不互质),则不存在逆元,返回-1 21 }
3.线性递推:
若做题时需要求出1~p范围内所有的逆元,用上述方法时间复杂度O(N logN)。若可线性递推,O(N)时间内即可求出所有答案。
递推式如下:
inv[i]=(p-p/i)*inv[p%i]%p
推导:
设 t=p/i k=p%i,那么
=> t*i+k≡0 (mod p)
=> -t*i≡k (mod p)
两边同除 i*k ,得
-t*inv[k]≡inv[i](mod p)
替换t k,得到
inv[i]=(p-p/i)*inv[p%i]%p
1 void init(){ 2 inv[1]=1; 3 for(int i=2;i<p;i++) { 4 inv[i]=(p-p/i)*A[p%i]%p; 5 } 6 }
4.特例
对于求阶乘的逆元也有一个递推式:
inv[i]=inv[i+1]*(i+1)%p
推导如下:
i ! · x ≡ 1 (mod p)
=> (i-1) ! · (i · x) ≡ 1 (mod p)
END
如发现错误 请各位神犇指正。