卡特兰数

概念:

卡特兰数并不是一个确定的数,而是一类数,是组合数学中一种常出现于各种计数问题中的数列。它没有一个明确的定义,但可以通过一些模型得出关于卡特兰数的很多信息,下面介绍几个这样的问题。

问题/模型:

(一)路径问题

image

给定 nn 的网格(上图是一个 66 的网格),初始你在左下角的格点 (00),每次你只能往右或者往上走一格,且不能穿过对角线(图中的蓝线),走到 (nn) 的方案数记作卡特兰数 C(n)

考虑离开 (00) 之后,第一次走到对角线上的位置为 (i+1i+1),将从 (00) 走到 (i+1i+1) 的过程 称为第一阶段,从 (i+1i+1) 走到 (nn) 的过程称为第二阶段,那么第二阶段的方案数显然是 C(n(i+1))=C(ni1);第一阶段的过程肯定是先往右边走到 (10),然后在不越过 y=x1 (即图中红线)的情况下到达 (i+1i),然后往上走一步到达 (i+1i+1),所以阶段 1 的方案数为 C(i)。所以总方案数为 C(n)=i=0n1C(i)C(ni1)

可以发现,在卡特兰数的问题中,一般常有两种选择,需要保证其中一种选择的数量在任意前缀中不大于另一种选择的数量。将上面的例子代入,可以发现两种选择分别为向上走或向右走,需要保证向上走的步数不大于向右走的步数。因此,我们除了可以根据它推出递推式以外,还可以得到一个通项公式。

考虑一共走 2n 步,如果y要走到终点,需要向上走 n步,向右走 n 步,只要向上走的步数比向右走的步数多一步,就会跨过蓝线,因此只要将所有的移动方式减去非法的移动方式,即可得到答案。

C(n)=(2nn)(2nn+1)

化简,得到另一个通项公式:

C(n)=(2nn)(n+1)

整理,又可以得到另一个递推式:

C(n)=2(2n1)n+1C(n1)

(二)合法括号序列

n 对括号形成的合法放置方案为 C(n)。如果 S 是一个合法方案,那么 (S) 也是合法方案;如果 S1S2 都是合法方案,那么 S1+S2 也是合法方案,此处的 + 表示两个括号序列接在一起。

两种选择分别为在当前位置上放左括号或右括号,要求保证在任何前缀中右括号的数量不大于左括号的数量。

(三)进出栈

n 个数依次进栈,出栈序列的方案数为 C(n)

两种选择分别为出栈和进栈,要求在操作的任何前缀保证出栈数量不大于进栈数量。

(四)凸多边形三角形划分

n+2 边形在端点之间连接一些互不相交的线段,将多边形划分成 n 个三角形,划分方案的数量为 C(n)

(五)二叉树种类

n 个节点的二叉树的种数为 C(n)

(四)(五)两个问题,类比(一)路径问题即可。

总结:

以卡特兰数为背景的题目的一大特点就是常常有两种选择,并需要保证在前缀中一种选择的数量不大于另一种选择(emmm也许还有多种选择的题目,只是我们遇到过23333)。同时,以卡特兰数为背景的题目也可以用动态规划来解决?想一想,为什么?观察一下卡特兰数的递推式,不就是很像动态规划吗(

习题:

(一)HDU 5184 Brackets

我们给出“正则括号”序列的归纳定义:
●空序列是正则括号序列,
●如果s是正则括号序列,则 (s) 是正则括号序列,
●如果 ab 是正则括号序列,则 ab 是正则括号序列。
●没有其他序列是常规括号序列
例如,以下所有字符序列都是常规括号序列:
(),(()),()(),()())
而以下字符序列不是:
(,),(((),()),(()
现在我们要构造一个长度的常规括号序列 n,当前面已经给出了几个括号时,我们可以得到多少个常规括号序列。

首先判断原序列是不是合法,如果不合法,即出现了右括号数量比左括号多的情况或 n 为奇数,直接输出 0。否则,可以统计出需要添加多少个右括号。将给出的括号序列补成一个正则括号后,可能序列长度还不到 n,因此还要将剩下的位置补全。设将原序列补充为正则括号后的长度为 s,则左括号和右括号分别还需要添加 ns2 个。

设将原序列补充为一个正则括号所需要的右括号的个数为 a,则右括号总共需要添加 a+ns2 个,左括号总共需要添加 ns2 个。

让我们结合一下上面提到的模型(二)合法括号序列,容易发现,所要添加的总括号长度是 a+ns 答案就是:

ans=(a+nsa+ns2)(a+nsa+ns2+1)

组合数要取模,逆元即可。

点击查看代码
#include<iostream> #include<string> #include<stack> #include<string.h> #define int long long using namespace std; const int MOD = 1e9 + 7; const int MAXN = 1e6 + 5+1e5+1e4; int inv[MAXN + 5]; char s[MAXN]; int f[MAXN + 5]; int qpow(int a,int n){ int ret = 1; while(n){ if(n & 1){ ret = (long long)ret * a % MOD; } n >>=1; a = (long long)a * a % MOD; } return ret; } int c(int n,int m){ if(m == -1)return 0; return (long long)f[n] * inv[m] % MOD * inv[n - m] % MOD; } int a,b,n; signed main(){ //ios::sync_with_stdio(false); //cin.tie(0); //cout.tie(0); f[0] = 1; for(int i = 1;i <= MAXN; i++){ f[i] = (long long)f[i - 1] * i % MOD; } inv[MAXN] = qpow(f[MAXN],MOD - 2); for(int i = MAXN - 1; i >= 0; i--) { inv[i] = (long long)inv[i + 1] * (i + 1) % MOD; } while(~scanf("%d%s",&n,s)){ int st = 0; a = b = 0; int siz = strlen(s); for(int i = 0; i < siz ; i++){ if(s[i] == '(')++st; if(s[i] == ')'){ if(st)st--; else a++; } } b += st; int k = n - siz - b; if(a > 0 ||(n%2 || k<0)){ cout << "0\n"; continue; } a += k / 2; b += k / 2; cout << (c(a + b,a) - c(a + b,a - 1) + MOD) % MOD<< "\n"; for(int i = 0; i < siz; i++){ s[i] = '\0'; } } }

(二)P3200 [HNOI2009]有趣的数列(?)

题目描述
我们称一个长度为 2n 的数列是有趣的,当且仅当该数列满足以下三个条件:
●它是从 12n2n 个整数的一个排列 {an}n=12n
●所有的奇数项满足 a1<a3<<a2n1 所有的偶数项满足 a2<a4<<a2n
●任意相邻的两项 a2i1a2i 满足:a2i1<a2i
对于给定的 n,请求出有多少个不同的长度为 2n 的有趣的数列。
因为最后的答案可能很大,所以只要求输出答案对 p 取模。

注意到,由于所有的奇数项都是递增的,且任意相邻的两项 a2i1a2i 满足:a2i1<a2i,容易得到,所有偶数项上的数大于前面的所有数,再推导一下,所有偶数项的数的大小至少等于其下标。

在数列的前 a 项中,下标为偶数的项一共有 n/2 项。所以前 i 位所放置的偶数不能超过 n/2 个,结合卡特兰数即可解。


__EOF__

本文作者Never Gonna Give You Up!
本文链接https://www.cnblogs.com/CZ-9/p/16591148.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   腾云今天首飞了吗  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示