卡特兰数专题(Catalan)
卡特兰数专题()
一、什么是卡特兰数?
明安图数,又称卡塔兰数,英文名 ,是组合数学中一个常出现于各种计数问题中的数列。以中国蒙古族数学家明安图 和比利时的数学家欧仁·查理·卡塔兰 的名字来命名,其前几项为(从第零项开始) :
知乎 卡特兰数四连击
https://zhuanlan.zhihu.com/p/31317307
https://zhuanlan.zhihu.com/p/31526354
https://zhuanlan.zhihu.com/p/31585260
https://zhuanlan.zhihu.com/p/31050581
卡特兰数的几何意义
简单来说,卡特兰数就是一个有规律的数列,在坐标图中可以表示为:从原点出发,每次向轴或者轴正方向移动个单位,直到到达点,且在移动过程中不越过第一象限平分线的移动方案总数。

卡特兰数公式推导
我们暂且先不考虑移动过程中不越过第一象限平分线这个约束条件,那么从点到点的过程中,我们总共需要向右移动步,向上移动步,一共步。我们可以理解为在步里面选出步来向上移动,那么剩下的步就是向右移动的步数,那么方案总数就是
现在,我们来看看如何解决不越过第一象限平分线这个问题。仔细想想,不越过第一象限平分线也就等价于不触碰到这条直线。而我们如果把触碰到了直线的路线的第一个与的触碰点之后的路线关于直线对称,并画出对称后的路线

黄海解读
我们会发现触碰到了直线的路径的终点都变成了点。也就是说,从点到点的路线当中触碰了直线的路线条数与从点到点的路线条数的数量是相等的。于是从点到点的非法路径条数为 。
综上所述,从点到点满足条件的路径数为
卡特兰数通项公式I
$$\huge f(n)=C_{2n}{n}-C_{2n}$$通过化简,公式可以简化为:
卡特兰数通项公式II
除了这个通项公式之外,卡特兰数还有一个由该通项公式推导而来的递推公式:
初始值:f[0] = f[1] = 1
卡特兰数公式III(递推)
卡特兰数公式IV(取模常用)
卡特兰数公式V(递推)
一般不用来实现,用来推规律
证明:在对括号的排列中,假设最后一个括号和第个左括号匹配。则在第个左括号之前,一定已经匹配上了()对左括号。如下图,因此,此种情况的数量为。()最后一个右括号可以个左括号匹配共种情况。

因此,对从到的情况求和得到,即可得到递推公式。
二、常见考点
1、 进出栈问题
设栈的初始状态为空,元素依次入栈,以下出栈序列有多少种可能性?注意:这个序列顺序是定的.
重点:归纳法思考,由大及小。
我们这样去想,假设最终的出栈序列可能性用表示,其中就是元素的个数。
假设第个数是最后出栈的数,那么:
- 比它小的前个数,肯定已经完成了入栈,出栈操作。因为从逻辑顺序上来讲,它们无法压到下面去吧。
- 比它大的后个数,肯定已经完成了入栈,出栈操作。它们倒是可以压到下面去,但假设是最后一个出栈的,它们不能破坏掉假设,也必须提出出栈。
现在,将原来的问题,划分为两个子问题和,根据乘法原理,结果就是。
的取值范围是,再根据加法原理:
展开写就是:
代码实现:
f[0] = 1;
for (int i = 1; i <= 12; i++) {
int fi = 0;
for (int j = 0; j <= i - 1; j++) fi += f[j] * f[i - j - 1];
cout << fi << " ";
}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 20;
int f[N];
int main() {
int n;
cin >> n;
f[0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
f[i] += f[j - 1] * f[i - j];
cout << f[n] << endl;
return 0;
}
2、排队问题
变种(排队问题):出栈入栈问题有许多的变种,比如个人拿元、个人拿元买物品,物品元,老板没零钱。问有几种排队方式。熟悉栈的同学很容易就能把这个问题转换为栈。值得注意的是,由于每个拿元的人排队的次序不是固定的,所以最后求得的答案要。拿元的人同理,所以还要。所以这种变种的最后答案为。
P1754 球迷购票问题
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 30;
LL f[N];
int main() {
int n;
cin >> n;
f[0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
f[i] += f[j - 1] * f[i - j];
cout << f[n] << endl;
return 0;
}
3、 二叉树构成问题
有个结点,问总共能构成几种不同的二叉树。
我们可以假设,如果采用中序遍历的话,根结点第个被访问到,则根结点的左子树有个点、根结点的右指数有个点。的取值范围为到。讲到这里就很明显看得出是卡特兰数了。
AcWing 1645. 不同的二叉搜索树
没有超过 的存储范围的话,可以使用递推;
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010, MOD = 1e9 + 7;
int n;
int f[N];
int main() {
cin >> n;
f[0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
f[i] = (f[i] + (LL)f[j - 1] * f[i - j]) % MOD;
cout << f[n] << endl;
return 0;
}
AcWing 1317. 树屋阶梯
模拟,按照公式(1)进行模拟,需要使用高精度
黄海的题解
#include <bits/stdc++.h>
using namespace std;
const int N = 10010; // 5000*2+10
int primes[N], cnt;
bool st[N];
int a[N], b[N];
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] * i <= n; j++) {
st[i * primes[j]] = true;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int p) { // n!中p的次数
int s = 0;
while (n) n /= p, s += n;
return s;
}
void mul(int a[], int b, int &len) {
int t = 0;
for (int i = 1; i <= len; i++) {
t += a[i] * b;
a[i] = t % 10;
t /= 10;
}
while (t) {
a[++len] = t % 10;
t /= 10;
}
//去前导0
while (len > 1 && !a[len]) len--;
}
int C(int a, int b, int c[]) {
int len = 1;
c[1] = 1;
for (int i = 0; i < cnt; i++) {
int p = primes[i];
int s = get(a, p) - get(b, p) - get(a - b, p);
while (s--) mul(c, p, len);
}
return len;
}
void sub(int a[], int b[], int &len) {
int t = 0;
for (int i = 1; i <= len; i++) {
t = a[i] - b[i] - t;
a[i] = (t + 10) % 10;
t < 0 ? t = 1 : t = 0;
}
while (len > 1 && !a[len]) len--;
}
int main() {
//加快读入
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
get_primes(N - 10);
int n;
cin >> n;
int a1 = C(n + n, n, a);
int b1 = C(n + n, n - 1, b); // bl下面没有用到,原因是两数相减,我们知道a>b,按着a的长度来就行了
sub(a, b, a1);
for (int i = a1; i >= 1; i--) printf("%d", a[i]);
return 0;
}
4、序列
给出一个,要求一个长度为的序列,使得序列的任意前缀中的个数不少于的个数,有多少个不同的序列?
以下为长度为的序列:
111000 101100 101010 110010 110100
类比一下括号问题,此题就是一个祼的卡特兰数问题。
5、 序列
个和个构成的项 ,其部分和满足非负性质,即 ,有多少个不同的此序列?
此典例解析与序列解析一模一样,即此数列的个数等于第个数,此处就不再赘述。
6、凸多边形划分
在一个边形中,通过不相交于边形内部的对角线,把边形拆分为若干个三角形,问有多少种拆分方案?
如五边形有如下种拆分方案:
如六边形有如下14种拆分方案:
结论:对凸边形进行不同的三角形分割(只连接顶点对形成个三角形)数为
这也是非常经典的一道题。我们可以这样来看,选择一个 基边 ,显然这是多边形划分完之后某个三角形的一条边。图中我们假设基边是,我们就可以用 和另外一个点假设为做一个三角形,并将多边形分成三部分(要是与挨着的话,就是两部分),除了中间的三角形之外,一边是边形,另一边是边形。的取值范围是到。所以本题的解
令则
很明显,这就是一个卡特兰数了。
四、链接资源
五、递推与递归的代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
// 学习视频
// https://www.bilibili.com/video/BV1nE411A7ST
int g[N];
// 递归
int f(int n) {
if (n == 0 || n == 1) return 1;
int sum = 0;
// f(0)*f(n-1)+f(1)*f(n-2)+....+f(n-1)*f(0)
for (int i = 0; i < n; i++)
sum += f(i) * f(n - 1 - i);
return sum;
}
int main() {
// 1、递推法
g[0] = g[1] = 1;
for (int i = 2; i < N; i++)
for (int j = 0; j < i; j++)
g[i] += g[j] * g[i - j - 1]; // 考虑思路:画括号法
for (int i = 0; i < N; i++)
cout << g[i] << endl;
// 2 、递归法
for (int i = 0; i < N; i++)
cout << f(i) << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2022-11-10 01分数规划总结
2021-11-10 P5729 工艺品制作
2021-11-10 P5728 【深基5.例5】旗鼓相当的对手
2021-11-10 P1047 [NOIP2005 普及组] 校门外的树
2021-11-10 P5727 【深基5.例3】冰雹猜想
2021-11-10 P1427 小鱼的数字游戏
2021-11-10 P1428 小鱼比可爱