乘法逆元

求解逆元有如下几种方式:

1. 如果 mod 为 素数,那么可以使用费马小定理+快速幂求解

2. 如果 gcd(a,mod)==1 其中,a为求逆元素,并且满足前面的等式保证a存在逆元。 那么可以使用 扩展欧几里得

3. 递推法快速打表求解多个逆元, 要求 mod 必须为奇质数

4. 如果 mod 不为素数,那么可以使用通用解法,这里不详细讲(因为我也不会

下面讲解1,2,3方法

方法一:(如果 mod 为 素数,那么可以使用费马小定理+快速幂求解)

1. 费马小定理

如果 gcd(a,p) == 1, 并且 p 为素数,那么就有 

公式变形:

所以:求解 a 的逆元,就是求解 。因此需要使用到快速幂

2. 代码:求 a 在 mod 下的乘法逆元

 1 //求 m^n % k 
 2 ll quick_power(ll m, ll n, ll k){
 3     ll ans = 1;
 4     m %= k;
 5     while(n){
 6         if(n & 1)
 7             ans = ans * m % k;
 8         n >>= 1;
 9         m = m * m % k;
10     }
11     return ans;
12 }
13 
14 ll inverse_quick_pow(ll a, ll mod){
15     return quick_power(a,mod-2,mod);
16 }

 

方法二:如果 gcd(a,mod)==1 其中,a为求逆元素,并且满足前面的等式保证a存在逆元。 那么可以使用 扩展欧几里得

1. 扩展欧几里得:传送门

2. 由于求解逆元,其实就是解方程 ax+py ≡ 1 (mod p)

3. 代码: 求 a 在 mod 下的乘法逆元

 1 //ax + by = gcd(a,b)
 2 //gcd 保存了 a,b 的最大公约数
 3 void ext_gcd(ll a, ll b, ll &gcd, ll &x, ll &y) {
 4     if (!b) { gcd=a; x=1; y=0; }
 5     else { ext_gcd(b,a%b,gcd,y,x); y-=(a/b)*x; }
 6     // 理解上面这个这句话就用上面的 extend_gcd 来理解,主要是使用了引用的概念
 7     // 并且 ext_gcd 在 y-=(a/b)*x 上面,也就是说 ext_gcd 参数中的 x,y 都是
 8     // 下一层函数返回的 x2, y2
 9     // 然后使用 y-=(a/b)*x 处理一下本层函数 y1 即可
10 }
11 
12 
13 ll inverse_ext_gcd(ll a, ll mod) {
14     ll x, y, gcd;
15     ext_gcd(a, mod, gcd, x, y);
16     // gcd(x,mod) ==1 是有逆元的充要条件
17     // 当满足有逆元的时候,需要调整 x 到 0-mod 之间
18     // 对于负数 x+mod 即可调整正确,
19     // 为了编码统一将正数页进行调整, (x+mod)%mod
20     return gcd == 1 ? (x + mod) % mod : -1;
21 }

 

 方法三:递推法快速打表求解多个逆元, 要求 mod 必须为奇质数

1. 原理:基于一个公式  

2. 初始条件: inv[1]=1;

3. 代码:

 1 // 递推法快速求 逆元
 2 const int maxn = 3e3 + 5;
 3 ll inv[maxn];
 4 
 5 //要求: mod 为奇质数
 6 //公式: inv[i]=(mod - mod /i) * inv[mod%i]%mod
 7 //初始条件: inv[1]=1;
 8 void inverse_recursion(ll mod) {
 9     inv[1] = 1;
10     for (int i = 2; i < maxn; i++)
11         inv[i] = (mod - mod / i) * inv[mod % i] % mod;
12 }

 

posted @ 2018-07-30 16:36  佰大于  阅读(363)  评论(0编辑  收藏  举报