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=3 时,有 5 种:((())), (()()), (())(), ()(()), ()()()。
📝 算法流程
- 公式法: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 数 + 高精度 |