卡特兰数
卡特兰数是在一系列计数问题中频繁出现的一条序列c={1,1,2,5,14,42,132,429,1430,4862,…}。该序列的通项公式为c(n)=Cn2∗n/(n+1),其中n的取值范围为所有自然数。应用场景和数学理论都可以推导出这个公式,本帖重点在应用场景中的推导。
卡特兰数在考研数据结构中有一个很经典的问题:将一些元素按照某种顺序入栈,并允许入栈和出栈两种操作交错,可以得到的出栈顺序有几种?
对于该问题中的所有元素,假设其数量为n,那么将其全部入栈再出栈,就对应了有2∗n个操作(n次入栈和n次出栈)。由于入栈元素顺序已经固定,因此只需要在2∗n个位置中选择n个作为“入栈”即可,总数为Cn2∗n。但是,要产生合理的出栈顺序,必须满足以下条件:
1. 所有操作执行完之后,栈内必须不含任何元素,即“入栈”操作不能连续的出现在最后;
2. 一个元素只有“入栈”了才能有后续“出栈”行为,即操作序列的任意前缀中,“入栈”操作的数量不能少于“出栈”操作的数量。
不满足以上条件的,均为不合法序列,它们的共同特点为:必然存在某一个奇数位序2∗m+1,在以该位置为端点的前缀中,包含(m+1)次出栈操作和m次入栈操作,而其余部分则正好相反,入栈操作比出栈操作多1。将剩余部分的操作取反,即“入栈”视为“出栈”,“出栈”视为“入栈”,则整个序列中,就包含了(n+1)次出栈和(n−1)次入栈,因此任意一种不合法序列都对应了某一种含有了(n+1)次出栈和(n−1)次入栈的序列,这些共有Cn+12∗n种。从总数中减去不合法数量,就是结果。结合组合数的公式,它可以改写为Cn2∗n/(n+1),即第n个卡特兰数c(n)
对于该问题中的0和1,分别将其视作“入栈”和“出栈”即可。考虑到取模需求,除(n+1)可以改为乘(n+1)的逆元,下面是完整代码,组合数求解的各种方法已经前述备至,故不在注释中额外解释了
C++ 代码
#include <iostream>
using namespace std;
const size_t MOD = 1e9 + 7;
size_t quickPower(size_t base, size_t power, size_t mod) {
size_t ans = 1;
while (power > 0) {
if (power & 1) ans = (ans * base) % mod;
base = (base * base) % mod;
power >>= 1;
}
return ans;
}
size_t combine(size_t n, size_t m, size_t p) {
size_t ans = 1;
while (m > 0) {
ans = (ans * (n + 1 - m)) % p;
ans = (ans * quickPower(m, p - 2, p)) % p;
m--;
}
return ans;
}
size_t catalanNum(size_t n, size_t p) {
size_t cb = combine(2 * n, n, p);
return (cb * quickPower(n + 1, p - 2, p)) % p;
}
int main() {
int n;
cin >> n;
cout << catalanNum(n, MOD) << endl;
return 0;
}