引言
在算法竞赛和组合数学问题中,组合数计算是一项基础而重要的操作。本文纯属板子,有兴趣的可以取搜索相应的博客。
代码实现与注释
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; // 应用组合数公式
}
算法分析
-
预处理阶段:
- 计算阶乘数组:O(N)O(N)
- 计算逆元数组:O(N+logmod)O(N+logmod)(一次快速幂+递推)
-
查询阶段:
- 每次组合数查询:O(1)O(1)
-
空间复杂度:
- O(N)O(N)(存储阶乘和逆元数组)