一、介绍
卡塔兰数是组合数学中一个常在各种计数问题中出现的数列。以比利时的数学家欧仁·查理·卡塔兰(1814–1894)命名。
历史上,清代数学家明安图(1692年-1763年)在其《割圜密率捷法》最早用到“卡塔兰数”。
卡特兰序列的前11项为:1, 1, 2, 5,14, 42, 132, 429, 1430, 4862, 16796。
二、性质
2.1 通项公式
2.2 递推关系
a.b.
这提供了一个更快速的方法来计算卡塔兰数。卡塔兰数的渐近增长为:
它的含义是当n → ∞时,左式除以右式的商趋向于1。(这可以用n!的斯特灵公式来证明。)
c. 所有的奇卡塔兰数Cn都满足。所有其他的卡塔兰数都是偶数。
d. 当n > 4 是,Cn 不是素数。{Cn | (2n)! 推出 Cn若是素数则Cn < 2n 推出 n <4 }。
三、等价问题
(1)给定n个元素,依次通过一个栈,求可能的出栈序列的个数。
在2n位二进制数中填入n个1的方案数为c(2n,n)。从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求。
不符合要求的数的特征是由左而右扫描时,必然在某一奇数位2m+1位上首先出现m+1个0的累计数和m个1的累计数,此后的2(n-m)-1位上有n-m个1和n-m-1个0。如若把后面这2(n-m)-1位上的0和1互换,使之成为n-m个0和n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n+1个0和n-1个1组成的排列。
反过来,任何一个由n+1个0和n-1个1组成的2n位二进制数,由于0的个数多2个,2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面部分0和1互换,使之成为由n个0和n个1组成的2n位数,即n+1个0和n-1个1组成的2n位数必对应一个不符合要求的数。
因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应。
显然,不符合要求的方案数为C(2n,n+1)。由此得出输出序列的总数目为C(2n,n) - C(2n,n+1) = 1/(n+1)*C(2n,n)。
(2)n个左括号和那个右括号组成的合法匹配的字符串的个数。
解:这个题目只要将上面证明过程中的1对应左括号,0对应右括号即可。
(3)n+1个数字连乘,不同的计算顺序个数。
解:这个就相当于加入了n对合法的括号。
(4)Cn表示长度2n的dyck word的个数。
(Dyck word是一个有n个X和n个Y组成的字串,且所有的部分字串皆满足X的个数大于等于Y的个数。)
解:(1)中X对应1,Y对应0,即可。
(5)n个节点的二叉树的可能形态的种类数。
解:(2)中的每个不同的括号串,一一映射于二叉树的每一种情况。
这里利用另一个通项公式推导一下:
设n个节点的二叉树有F(n)个,则分为根的左子树0各节点、右子树n-1个节点,跟的左子树1个节点、右子树n-2个节点,...:
F(n)= F(0)*F(n-1)+ F(1)*F(n-2)+ ... + F(n-1)*F(0)= Cn。
(6)n个非叶节点的满二叉树的形态数。(对称后得到的二叉树除非自己本身对称,否则算是不同)
设n个节点的二叉树有F(n)个,则分为根1为根,二为根,...,n为根:
F(n)= F(0)*F(n-1)+ F(1)*F(n-2)+ ... + F(n-1)*F(0)= Cn。
(7)对于一个n*n的网格,每次我们能向右或者向上移动一格,那么从左下角到右上角的所有在副对角线下方的路径数。
解:我们将一条水平边记为1,垂直边记为0,那么就组成了一个n个,1和n个0的序列,同(1)。
(8)凸n+2边形进行三角形分割数。(只连接顶点对形成n个三角形)
解:将节点按顺时针办好1~n,枚举1和其他所有点的连线情况,则:
F(n)= Σ(F(k)F(n+2-k)){2< k < n};解得 F(n) = Cn-2。
(9)圆周上2n-2个点的不相交连弦方式。(凸2n-2多边形的顶点也成立)
解:设Bn为2n-2个点的不相交连弦方式数,则任取一点A有n-1中连弦方式(两侧必须是偶数个点)
两侧分别是2(k-1)和2(n-k-1)个点,分别为Bk和Bn-k种方式,因此Bn = Cn-1。
(10)对于集合的不交叉划分的数目。
对于集合{a,b}和{c,d},假设他们组成了两个区间[a,b]和[c,d],我们假设两个区间不重合,那么以下四种情况当做是不交叉的:a<c<d<b,a<b<c<d,c<a<b<d与c<d<a<b,就是说两个区间可以包含或者相离,那么此时我们称集合{a,b}和{c,d}是不交叉的。对于集合,将里面元素两两分为一子集,共n个,若任意两个子集都是不交叉的,那么我们称此时的这个划分为一个不交叉划分。
解:我们将每个子集中较小的数用左括号代替,较大的用右括号代替,那么就是(2)。
(11)n层的阶梯切割为n个矩形的切法数。
解:我们先绘制如下的一张图片,即n为5的时候的阶梯:
我们注意到每个切割出来的矩形都必需包括一块标示为*的小正方形,那么我们此时枚举每个*与#标示的两角作为矩形;
剩下的两个小阶梯就是我们的两个更小的子问题了,于是我们的注意到这里的式子就是Cn的递推公式了。
(12)在一个2*n的格子中填入1到2n使得每个格子内的数值都比其右边和上边的所有数值都小的情况数。
解:可以转化成括号匹配问题。
(13)2n个不同实数分成两组A = {a1,a2...,an},B= {b1,b2...,bn},使得对应的数字均有ai<bi的拆分数。
解:和上面问题等价。
(14)2n-2张选票,投给甲乙,甲任意时刻得票多于乙的情况数。
解:即01串的入栈出栈证明。
(15)不定方程 Σxi = n-1(1到n-1),满足Σ xi ≥ k (1到k)的解的个数。
解:同上。
四、具体题目
4.1 UVa 991 - Safe Salutations
求圆内不相交弦的连法数
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
int f[22][22] = {0};
int main()
{
for (int i = 0 ; i <= 20 ; ++ i)
f[i][0] = f[i][i] = 1;
for (int i = 1 ; i <= 20 ; ++ i)
for (int j = 1 ; j < i ; ++ j)
f[i][j] = f[i-1][j]+f[i-1][j-1];
int n,t = 0;
while (cin >> n) {
if (t ++) cout << endl;
cout << f[2*n][n]/(n+1) << endl;
}
return 0;
}
4.2 UVa 10303 - How Many Trees?
统计BST个数
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
int C[1005][1005] = {0};
int main()
{
C[1][0] = 1;
for (int i = 2 ; i < 1001 ; ++ i) {
for (int j = 0 ; j < 1000 ; ++ j)
C[i][j] += C[i-1][j]*(4*i-2);
for (int j = 0 ; j < 1000 ; ++ j) {
C[i][j+1] += C[i][j]/10;
C[i][j] %= 10;
}
for (int j = 999 ; j >= 0 ; -- j) {
C[i][j-1] += C[i][j]%(i+1)*10;
C[i][j] /= (i+1);
}
}
int n;
while (cin >> n) {
int end = 999;
while (!C[n][end]) -- end;
while (end >= 0) printf("%d",C[n][end --]);
printf("\n");
}
return 0;
}
4.3 UVa 10007 - Count the Trees
统计二叉树的个数,由于求的不是树的形状数,而是所有树的个数,所以本题要乘以n!
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
int C[305][2005] = {0};
int main()
{
C[1][0] = 1;
for (int i = 2 ; i < 301 ; ++ i) {
for (int j = 0 ; j < 2000 ; ++ j)
C[i][j] += C[i-1][j]*(4*i-2);
for (int j = 0 ; j < 2000 ; ++ j) {
C[i][j+1] += C[i][j]/10;
C[i][j] %= 10;
}
for (int j = 1999 ; j >= 0 ; -- j) {
C[i][j-1] += C[i][j]%(i+1)*10;
C[i][j] /= (i+1);
}
for (int j = 0 ; j < 2000 ; ++ j)
C[i][j] *= i;
for (int j = 0 ; j < 2000 ; ++ j) {
C[i][j+1] += C[i][j]/10;
C[i][j] %= 10;
}
}
int n;
while (cin >> n && n) {
int end = 1999;
while (!C[n][end]) -- end;
while (end >= 0) printf("%d",C[n][end --]);
printf("\n");
}
return 0;
}
4.4 UVa 1478 - Delta Wave
求从(0,0)到(N,0)的路径数,每次可以斜向上或者斜向下或者直走。
因为不能走到负的区域,所以上升的和下降的此时必然相等,而且上升次数要随时不小于下降次数。
由此可知,上升和下降是上面提到的括号合法匹配,枚举所有的上升下降次数有:
边长为n的图中走法数 F(n)= Σ(C(n,2*i)*Ci),其中:
C(n,2*i)是n条路中有i条上升和i条下降的方案数,Ci是i条上升和i条下降的合法组合数(内部)。
化简:F(n)= Σ(C(n,2*k)*C(2k,k)/(k+1));
设: B(k)= C(n,2*k)*C(2k,k)/(k+1)
得递推关系:B(k)= B(k-1)*(n-2*k+2)*(n-2*k+1)/(k*(k+1))
利用上面递推关系计算即可。(貌似java的大数用起来比较快╮(╯▽╰)╭)
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
int C[4808] = {0};
int ans[4808];
int main()
{
int n;
while (~scanf("%d",&n) && n) {
memset(C, 0, sizeof(C));
memset(ans, 0, sizeof(ans));
C[0] = 1;ans[0] = 1;
for (int i = 1 ; 2*i <= n ; ++ i) {
for (int j = 0 ; j < 4800 ; ++ j)
C[j] *= (n-2*i+2)*(n-2*i+1);
for (int j = 0 ; j < 4800 ; ++ j) {
C[j+1] += C[j]/10;
C[j] %= 10;
}
for (int j = 4799 ; j >= 0 ; -- j) {
C[j-1] += C[j]%((i+1)*i)*10;
C[j] /= ((i+1)*i);
}
for (int j = 0 ; j < 4800 ; ++ j)
ans[j] += C[j];
for (int j = 0 ; j < 4800 ; ++ j) {
ans[j+1] += ans[j]/10;
ans[j] %= 10;
}
}
int end = 99;
while (!ans[end]) -- end;
while (end >= 0) printf("%d",ans[end --]);
printf("\n");
}
return 0;
}