Catalan 数
💡 核心思想
Catalan 数是一组重要的组合数,出现在多种计数问题中:合法的括号序列数、二叉树形态数、网格路径不跨越对角线的方案数、出栈顺序数等。第 n 个 Catalan 数为 C(2n,n)/(n+1) = C(2n,n) - C(2n,n+1)。递推式:C₀=1, Cₙ₊₁ = Σ Cᵢ·Cₙ₋ᵢ。
Catalan 数是竞赛中的"常客",看到"合法的括号序列"、"不跨越对角线的路径"、"二叉树的形态",就要想到 Catalan。记住公式和前几项:1, 1, 2, 5, 14, 42, 132, 429... 很多时候不需要现场推导,直接套公式就行。
🎯 直觉理解
Catalan 数是一系列"看似不同、本质相同"的组合计数的答案:n 对括号的合法括号序列数、n 个节点的二叉搜索树数、$n imes n$ 网格不越过对角线的路径数……它们都等于 $C_n=\frac{1}{n+1}C(2n,n)$。
递推:$C_0=1$,$C_n=sum_{i=0}^{n-1}C_i imes C_{n-1-i}$——"把第一个元素的划分枚举出来":左边 i 个、右边 n-1-i 个,两部分独立计数相乘再求和。
做题套路:先背前几项(1, 1, 2, 5, 14, 42, 132),看到"括号匹配、进出栈、二叉树计数、不越线路径"这些关键词,验证几项符合就套 Catalan。
📝 算法流程
- 公式法:Cat(n) = C(2n, n) / (n + 1)
- 递推法:Cat(0) = 1, Cat(n+1) = Σ Cat(i)·Cat(n-i)
- 线性递推:Cat(n) = Cat(n-1) · (4n-2) / (n+1)
- 预处理阶乘+逆元可在 O(1) 查询
$$Cat_n = \frac{1}{n+1} \binom{2n}{n} = \binom{2n}{n} - \binom{2n}{n+1}$$
$$Cat_{n+1} = \sum_{i=0}^{n} Cat_i \cdot Cat_{n-i}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(n)$ 递推 / $O(1)$ 阶乘查询 |
| 空间 | $O(n)$ |
💻 参考实现(C++)
C++ (C++17)
#include
using namespace std;
using ll = long long;
const int MOD = 1e9 + 7;
ll fac[200005], inv[200005];
ll qpow(ll a, ll n) {
ll res = 1; a %= MOD;
while (n) { if (n & 1) res = res * a % MOD; a = a * a % MOD; n >>= 1; }
return res;
}
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(ll n, ll m) {
if (m > n || m < 0) return 0;
return fac[n] * inv[m] % MOD * inv[n-m] % MOD;
}
ll catalan(int n) {
return C(2*n, n) * qpow(n + 1, MOD - 2) % MOD;
}
int main() {
init(100000);
for (int i = 0; i <= 10; i++)
cout << "Cat(" << i << ")=" << catalan(i) << " ";
// Cat(0)=1 Cat(1)=1 Cat(2)=2 Cat(3)=5 Cat(4)=14 ...
cout << endl;
return 0;
} ⚠️ 常见坑点
模意义下除法——要用逆元,不能直接除
递推边界——Cat(0) = 1,不是 0
组合数公式中的 n 和 2n——容易混淆
应用场景识别——不是所有括号问题都是 Catalan
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P1044 栈 | 洛谷 | CSP-S | Catalan 数模板题 |
| P3200 有趣的数列 | 洛谷 | CSP-S | Catalan 数应用 |
| P2532 树屋 | 洛谷 | CSP-S | Catalan 数 + 高精度 |