逆元+快速幂求组合数

128 阅读2分钟

引言

在算法竞赛和组合数学问题中,组合数计算是一项基础而重要的操作。本文纯属板子,有兴趣的可以取搜索相应的博客。

代码实现与注释

const int mod = 1e9 + 7;  // 大质数模数,满足费马小定理条件
const int N = 2e5 + 10;   // 预处理的最大范围
int inv[N], fac[N];       // inv存储逆元,fac存储阶乘

/**
 * 快速幂算法:计算 a^b mod mod
 * @param a 底数
 * @param b 指数
 * @return a^b mod mod
 */
int quickpow(int a, int b) {
  int res = 1;            // 初始化结果为1(a^0 = 1)
  while (b) {             // 当指数不为0时循环
    if (b & 1)            // 如果当前最低位为1(b为奇数)
      res = res * a % mod; // 将a的当前幂乘入结果
    a = a * a % mod;      // a自平方,计算a^(2^k)
    b >>= 1;              // 指数右移一位(相当于b /= 2)
  }
  return res;
}

/**
 * 初始化函数:预计算阶乘和逆元数组
 */
void init() {
  fac[0] = inv[0] = 1;    // 0! = 1,0!的逆元也是1
  
  // 计算阶乘数组:fac[i] = i! mod mod
  for (int i = 1; i <= N; i++)
    fac[i] = fac[i - 1] * i % mod;
  
  // 使用费马小定理计算N!的逆元
  inv[N] = quickpow(fac[N], mod - 2); // fac[N]^(mod-2) ≡ fac[N]^{-1} mod mod
  
  // 递推计算其他逆元
  for (int i = N - 1; i >= 1; i--)
    inv[i] = inv[i + 1] * (i + 1) % mod; // 应用递推公式
}

/**
 * 组合数计算函数:C(n, m) = n! / (m!(n-m)!)
 * @param n 总数
 * @param m 选取数
 * @return C(n, m) mod mod
 */
int C(int n, int m) {
  if (n < 0 || m < 0 || m > n) return 0; // 非法参数处理
  if (m == 0 || m == n) return 1;        // C(n,0) = C(n,n) = 1
  return fac[n] * inv[m] % mod * inv[n - m] % mod; // 应用组合数公式
}

算法分析

  1. 预处理阶段

    • 计算阶乘数组:O(N)O(N)
    • 计算逆元数组:O(N+log⁡mod)O(N+logmod)(一次快速幂+递推)
  2. 查询阶段

    • 每次组合数查询:O(1)O(1)
  3. 空间复杂度

    • O(N)O(N)(存储阶乘和逆元数组)