乘法逆元小结
乘法逆元:在模意义下 乘法和加法都是合法的,但除法并不合法,为了解决除法的问题,引进了乘法逆元 inv(a),记作 a-1 ,即像满足 a*inv(a) ≡ 1 (mod p)这样的 a 的倒数的数 inv(a) 的存在。
处理逆元的几种方法:
1、暴力求解
复杂度:O( p ) p 为 模。
假设要求 x 在 mod p 情况下的逆元,暴力枚举 p-1 个数,满足条件:x * num ≡ 1 (mod p);
2、费马小定理 && 欧拉定理
复杂度:O(log2N)
费马小定理:a(p-1) ≡ 1 (mod p) 【前提 p为质数】
可得 inv(a) = a(p-2) ;
欧拉定理:aphi( p ) ≡ 1 (mod p) 【前提 p不为质数】当然 p 不为质数的情况下不一定每一个数都存在逆元。
可得 inv(a) = aphi(p)-1 ;
即根据 p 的情况,选用快速幂求解。
1 LL pow_mod(LL a, LL b, LL p){ //a的b次方结果模p 2 LL ret = 1; 3 while(b){ 4 if(b & 1) ret = (ret * a) % p; 5 a = (a * a) % p; 6 b >>= 1; 7 } 8 return ret; 9 } 10 LL Fermat(LL a, LL p){ //费马求a关于p的逆元 11 return pow_mod(a, p-2, p); 12 }
3、拓展欧几里得(by 《挑战》)
由于方程 a * inv(a) ≡ 1 (mod p) 等价于存在一个整数 k 使得 a*inv(a) = 1+k*p;
即问题转换成 求解满足 a* inv(a) - k*p = 1 的 inv(a) 的问题,可以运用 extgcd (拓展欧几里得)求解。
同时!!! 如果 gcd(a, p) != 1 则逆元不存在。
1 int exgcd(int a,int b,int &x,int &y) 2 { 3 if(b==0) 4 { 5 x=1;y=0; 6 return a; 7 } 8 int d=exgcd(b,a%b,y,x); 9 y=y-a/b*x; 10 return d; 11 } 12 int mod_inverse(int a,int mod)//求a关于mod的逆元 13 { 14 int x,y; 15 exgcd(a,mod,x,y); 16 return (x%mod+mod)%mod; 17 }
4、万能公式法
以上几种方法都存在局限性,那么怎么办呢?
在 a | b 的情况下: a/b mod p = a mod (bp) / b
5、神奇线性大法预处理 1~N 的逆元
参考:http://blog.miskcoo.com/2014/09/linear-find-all-invert
模板:
inv[1] = 1;
inv [ i ] = (mod - mod/i)*inv[ mod%i ] %mod;
6、巧妙预处理阶乘逆元
在HDU多校一道莫队的题看到的技巧,当时就ORZ.....
http://acm.hdu.edu.cn/showproblem.php?pid=6333
首先快速幂求出 (maxn-1) ! 在mod的意义下的逆元;
逆推: inv[ i ] = (inv[ i + 1]*(i+1))%mod;
1 void init() 2 { 3 rev2 = q_pow(2, MOD-2); // 2的逆元 4 fac[0] = fac[1] = 1; 5 for(LL i = 2; i < MAXN; i++){ //预处理阶乘 6 fac[i] = fac[i-1]*i%MOD; 7 } 8 9 inv[MAXN-1] = q_pow(fac[MAXN-1], MOD-2); //逆推预处理阶乘的逆元 10 for(int i = MAXN-2; i >= 0; i--){ 11 inv[i] = inv[i+1]*(i+1)%MOD; 12 } 13 }
1 void Init() //预处理排列数和逆元 2 { 3 fac[0] = Inv[0] = fac[1] = Inv[1] = 1; 4 for(int i = 2; i < MAXN; i++) fac[i] = fac[i-1]*i%mod; 5 for(int i = 2; i < MAXN; i++) Inv[i] = (mod-mod/i)*Inv[mod%i]%mod; 6 for(int i = 2; i < MAXN; i++) Inv[i] = Inv[i]*Inv[i-1]%mod; 7 }