组合数学CSP-S

目录

💡 核心思想

组合数学研究计数问题。核心工具:排列 $A_n^k$、组合 $C_n^k$、杨辉三角、Lucas 定理。在模意义下计算组合数需要预处理阶乘和逆元。$C_n^k = \frac{n!}{k!(n-k)!}$。

组合数学在 CSP-S 中常考。你需要掌握:一是杨辉三角递推 $C_n^k = C_{n-1}^{k-1} + C_{n-1}^k$(适合 $n$ 不大的情况);二是阶乘 + 逆元预处理(适合多次查询);三是 Lucas 定理(适合 $n$ 很大但 $p$ 不大的情况)。

🎯 直觉理解

组合数 $C_n^k$ 就是从 $n$ 个人中选 $k$ 个人的选法数。杨辉三角中每个数等于它上方两个数之和——因为"从 $n$ 个人中选 $k$ 个"等于"选了第一个人后从剩下 $n-1$ 个中选 $k-1$ 个"加上"不选第一个人后从剩下 $n-1$ 个中选 $k$ 个"。

📝 算法流程

  1. 杨辉三角递推:$C[n][k] = C[n-1][k-1] + C[n-1][k]$
  2. 阶乘+逆元:$C_n^k = fac[n] \times inv[k] \times inv[n-k] \mod p$
  3. Lucas 定理:$C_n^k \equiv C_{n\mod p}^{k\mod p} \times C_{n/p}^{k/p} \pmod{p}$

$$C_n^k = \frac{n!}{k!(n-k)!}$$

$$C_n^k = C_{n-1}^{k-1} + C_{n-1}^k$$

📊 复杂度分析

指标复杂度
时间预处理 $O(n)$,查询 $O(1)$;Lucas $O(\log_p n)$
空间$O(n)$

💻 参考实现(C++)

C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int N = 1000005;
ll fac[N], inv[N];
ll qpow(ll a, ll n) { ll ans=1; while(n){if(n&1)ans=ans*a%MOD; a=a*a%MOD; n>>=1;} return ans; }
void init(int n) {
    fac[0] = 1;
    for (int i = 1; i <= n; i++) fac[i] = fac[i-1] * i % MOD;
    inv[n] = qpow(fac[n], MOD-2);
    for (int i = n-1; i >= 0; i--) inv[i] = inv[i+1] * (i+1) % MOD;
}
ll C(int n, int k) {
    if (k < 0 || k > n) return 0;
    return fac[n] * inv[k] % MOD * inv[n-k] % MOD;
}
int main() {
    int n, m; cin >> n >> m;
    init(max(n,m));
    cout << C(n, m) << endl;
    return 0;
}

⚠️ 常见坑点

阶乘和逆元的预处理范围要覆盖所有查询

Lucas 定理要求 $p$ 是质数

组合数结果可能很大必须取模

inv 的预处理要从后往前推

📚 相关题目

题目来源难度备注
P3807 Lucas定理洛谷CSP-SLucas模板
P2822 组合数问题洛谷CSP-S杨辉三角+前缀和