首页 > 代码库 > 卡特兰数

卡特兰数

卡特兰数

栈是一种常见的数据结构,有许多关于栈的问题,其中之一就是统计元素可能的出栈序列。具体说,就是给定n个元素,依次通过一个栈,求可能的出栈序列的个数。
如果我们用直接模拟的方法,当n较大时会很费时间;

例如动态规划f[i,j]表示栈内有i个元素且栈外有j个元素还未进栈,那么以进栈还是出栈为决策就马上得到了转移方程f[i,j]=f[i-1,j]+f[i+1,j-1]。如此一来,很多重复的计算得以免去,效率大幅提高(时间复杂度为指数级,大概为N^2级的算法)。

另一种方法是利用组合数学求出栈序列个数,得到公式:S=C(2n,n)-C(2n,n-1);

出栈次序问题。一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列?

分析:对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘1’,出栈设为状态‘0’n个数的所有状态对应n1n0组成的2n位二进制数。由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作数b大于等于出栈的操作数a(a≤b),因此输出序列的总数目=由左而右扫描由n1n0组成的2n位二进制数,1的累计数不小于0的累计数的方案种数。 

2n位二进制数中填入n1的方案数为c(2n,n),不填1的其余n位自动填0。从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求。 

不符合要求的数的特征是由左而右扫描时,必然在某一奇数位2m+1位上首先出现m+10的累计数和m1的累计数,此后的2(n-m)-1位上有n-m1n-m-10。如若把后面这2(n-m)-1位上的01互换,使之成为n-m0n-m-11,结果得1个由n+10n-11组成的2n位数,即一个不合要求的数对应于一个由n+10n-11组成的排列。 

反过来,任何一个由n+10n-11组成的2n位二进制数,由于0的个数多2个,2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面部分01互换,使之成为由n0n1组成的2n位数,即n+10n-11组成的2n位数必对应一个不符合要求的数。 

因而不合要求的2n位数与n10n11组成的排列一一对应。 

显然,不符合要求的方案数为c(2n,n+1)。由此得出 输出序列的总数目=c(2n,n)-c(2n,n+1)=1/(n+1)*c(2n,n)

类似题目:

   其中有一个类似的题目:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)

卡特兰数:

卡特兰数是组合数学中一个常出现在各种计数问题中出现的数列。由以比利时的数学家欧仁·查理·卡塔兰 (18141894)命名。

原理:
  h(0)=1,h(1)1,catalan数满足递归式:
  h(n)= h(0)*h(n-1) + h(1)*h(n-2) + .....+ h(n-1)h(0) (其中n>=2)

       另类递归式:  h(n)=((4*n-2)/(n+1))*h(n-1);

前几项为 (OEIS中的数列A000108: 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, ...

应用

组合数学中有非常多.的组合结构可以用卡塔兰数来计数。在Richard P. StanleyEnumerative Combinatorics: Volume 2一书的习题中包括了66个相异的可由卡塔兰数表达的组合结构。以下用Cn=3Cn=4举若干例:

  • Cn表示长度2ndyck word的个数。Dyck word是一个有nXnY组成的字串,且所有的部分字串皆满足X的个数大于等于Y的个数。以下为长度为6dyck words: 

XXXYYY XYXXYY XYXYXY XXYYXY XXYXYY

  • 将上例的X换成左括号,Y换成右括号,Cn表示所有包含n组括号的合法运算式的个数

((())) ()(()) ()()() (())() (()())

  • Cn表示有n+1个叶子的二叉树的个数。                                       
  • Cn表示所有不同构的含n个分枝结点的满二叉树的个数。(一个有根二叉树是满的当且仅当每个结点都有两个子树或没有子树。) 
  • Cn表示所有在n × n格点中不越过对角线的单调路径的个数。一个单调路径从格点左下角出发,在格点右上角结束,每一步均为向上或向右。计算这种路径的个数等价于计算Dyck word的个数: X代表向右Y代表向上
  • Cn表示通过连结顶点而将n + 2边的凸多边形分成三角形的方法个数
  • Cn表示对{1, ..., n}依序进出置换个数。一个置换w是依序进出栈的当S(w) = (1, ..., n), 其中S(w)递归定义如下:令w = unv,其中nw的最大元素,uv为更短的数列;再令S(w) =S(u)S(v)n,其中S为所有含一个元素的数列的单位元。 
  • Cn表示集合{1, ..., n}不交叉划分的个数.那么Cn 永远不大于第n贝尔数Cn也表示集合{1, ..., 2n}不交叉划分的个数,其中每个段落的长度为2。综合这两个结论,可以用数学归纳法证明 that all of the free cumulants of degree more than 2 of the Wigner semicircle law are zero. This law is important in free probability theory and the theory of random matrices
  • Cn表示用n个长方形填充一个高度为n的阶梯状图形的方法个数。
例题:

How Many Trees?

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 2757    Accepted Submission(s): 1615


Problem Description
A binary search tree is a binary tree with root k such that any node v reachable from its left has label (v) <label (k) and any node w reachable from its right has label (w) > label (k). It is a search structure which can find a node with label x in O(n log n) average time, where n is the size of the tree (number of vertices). 

Given a number n, can you tell how many different binary search trees may be constructed with a set of numbers of size n such that each element of the set will be associated to the label of exactly one node in a binary search tree? 
 

Input
The input will contain a number 1 <= i <= 100 per line representing the number of elements of the set.
 

Output
You have to print a line in the output for each entry with the answer to the previous question.
 

Sample Input
1 2 3
 

Sample Output
1 2 5
 

Source
UVA
 
AC代码:
#include<cstdio>
#include<cstring>
#define MAX 1000
#define BASE 10

using namespace std;

int Array[101][MAX];

void Multiply(int A[],int n,int num)//大数乘法
{
	int i;
	int sum=0;
	for(i=n-1;i>=0;i--)
	{
		sum+=num*A[i];
		A[i]=sum%BASE;
		sum/=BASE;
	}
}

void Divide(int A[],int n,int num)//大数除法
{
	int i;
	int div=0;
	for(i=0;i<n;i++)
	{
		div=div*BASE+A[i];
		A[i]=div/num;
		div%=num;
	}
}

int main(int argc,char *argv[])
{
	int i;
	int n;
	memset(Array[1],0,MAX*sizeof(int));
	for(i=2,Array[1][MAX-1]=1;i<101;i++)//高坐标存放大数低位
	{
		memcpy(Array[i],Array[i-1],MAX*sizeof(int));//h[i]=h[i-1];
		Multiply(Array[i],MAX,4*i-2);               //h[i]*=(4i-2);
		Divide(Array[i],MAX,i+1);                   //h[i]/=(i+1)
	}
	while(scanf("%d",&n)!=EOF)
	{
		for(i=0;i<MAX;i++)
		{
			if(Array[n][i]!=0)
				break;
		}
		printf("%d",Array[n][i]);// 输出第一个非零的数
		i++;
		for(;i<MAX;i++)
			printf("%d",Array[n][i]);
		printf("\n");
	}
	return 0;
}