乘法模运算逆元

逆元 (Multiplicative Inverse)

逆元是在特定运算下的"反转"
举几个例子

  1. 加法
    a的加法逆元就是-a, 它们相加为0
  2. 乘法逆元
    a的乘法逆元是1/a, 它们相乘为1
  3. 矩阵乘法
    矩阵的逆元是逆矩阵, 它们相乘为单位矩阵

有人会问了, 为什么它们运算结果并不一样呢? 他们的共性在哪里?
这里的关键在于, 他们的运算结果都是源于运算的"基本性质"和"单位元素"
所谓单位元素就是, 在加法中, 任意元素加0不会改变, 而乘法中, 所有元素乘以1不会改变, 这就是单位元素, 矩阵同理

乘法模运算逆元

很多算法题目中会涉及到乘法模运算的逆元
我举2个例子

  1. 题目给了100个数的乘积模1e9+7之后的结果, 然后给了后50个数乘积模1e9+7的结果
    请问前50个数的乘积模1e9+7是多少?
  2. 组合数学涉及到计算非常大的C(n, m) mod M 的时候

这都是需要"回撤"或"反转"的时候

求逆元算法

  1. 扩展欧几里得 (exgcd)
  2. 费马小定理 (需要模数M是素数)
  3. 欧拉定理

费马小定理

说一下费马小定理
因为它比较简单

费马小定理: 若M是质数,且B、M互质,那么B^(M-1) mod M = 1
所以可以扩展到
A/B mod M
= A/B mod M * 1
= A/B mod M * B^(M-1) mod M
= A*B^(M-2) mod M

于是除法就被转换成了乘法
这里求B的M-2幂次的时候用快速幂, 所以是个O(logn)的算法

计算C(n, m)

首先, 定义一下阶乘为fac(x)
组合数的公式很简单
C(n, m) = fac(n) / (fac(m) * fac(n-m))
那首先我们需要预处理两个数组
fac, inv
前者计算从1到n的阶乘的模M结果(复杂度O(n))
后者计算从1到n的阶乘的模M的逆元(复杂度O(n))

前面讲了单次求逆元复杂度是O(logn), 但是这里整体复杂度为什么不是O(nlogn)呢? 因为这里有个递推关系, 他们彼此之间是有依赖关系的, 并不需要每个数组元素都单独计算一遍
inv[i] * fac[i] == 1 (% MOD)
inv[i] * fac[i] * (i+1) == (i+1) (% MOD)
inv[i] * fac[i+1] == (i+1) (% MOD)
inv[i] = (i+1) / fac[i+1] (% MOD)
我们一开始这俩数组目的是什么? 是不是inv_arr[i] * f_arr[i] == 1 (%MOD)
注意到右边其实就是(i+1) * inv[i+1]
所以只要倒着算就可以递推了

void init()
{
    fac[0]=inv[0]=1;
    for (long long i = 1; i < maxn; i++)
    {
        fac[i] = fac[i - 1] * i % MOD;
    }
    inv[maxn-1] = quickpower(fac[maxn-1], MOD - 2);
    for(int i = maxn-2; i>=0; i--){
        inv[i] = inv[i+1] * (i+1) % MOD;
    }
}

所以C(n, m)是不是可以张口就来?
C(n, m) = fac[n] * inv[n-m] % MOD * inv[n] % MOD;
除法转换为乘法就可以了

完整小case

计算 fac(1到99) / fac(5到99) = 24

#include <bits/stdc++.h>
using namespace std;

const int MOD = 1e9 + 7;

long long pow(long long a, int b)
{
    long long ret = 1;
    while (b)
    {
        if (b & 1)
        {
            ret *= a;
            ret %= MOD;
        }
        a *= a;
        a %= MOD;
        b >>= 1;
    }
    cout << "ret " << ret << endl;
    return ret;
}

int main()
{
    long long a = 1;
    for (int i = 1; i < 100; i++)
    {
        a = a * i % MOD;
    }
    cout << a << endl;

    long long b = 1;
    for (int i = 5; i < 100; i++)
    {
        b = b * i % MOD;
    }
    cout << b << endl;

    // A/B MOD M = A * B^(M-2) MOD M

    long long c = a * pow(b, MOD - 2) % MOD;
    cout << c << endl;
    return 0;
}
// 费马小定理: 若M是质数, 且B, M互质, 那么 B^(M-1) MOD M = 1

posted on 2023-11-21 20:23  tianlonghuangwu  阅读(116)  评论(0)    收藏  举报

导航