【Catalan数——卡特兰数】
Catalan数是组合数学中一个常出现在各种计数问题中出现的数列。由以比利时的数学家欧仁·查理·卡塔兰 (1814–1894)命名。 原理:
令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)=C(2n,n)/(n + 1) (n=1,2,3,...)
前几项为 (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, ...
二.Catalan数的典型应用:
1.括号化问题。矩阵链乘: P=A1×A2×A3×……×An,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?
2.将多边行划分为三角形问题。将一个凸多边形区域分成三角形区域(划分线不交叉)的方法数?
类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?
3.出栈次序问题。一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列?
类似:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
类似:一位大城市的律师在他住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
分析:对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘1’,出栈设为状态‘0’。n个数的所有状态对应n个1和n个0组成的2n 位二进制数。由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作数b大于等于出栈的操作数a(a≤b),因此输出序列的总数目=由左而右扫描由n个1 和n个0组成的2n位二进制数,1的累计数不小于0的累计数的方案种数。
在2n位二进制数中填入n个1的方案数为c(2n,n),不填1的其余n位自动填0。从中减去不符合要求(由左而右扫描,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)。(这个公式的下标是从h(0)=1开始的)
1.括号化问题。
矩阵链乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?(h(n)种)
2.出栈次序问题。
一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列?
类似:
(1)有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
(2)在圆上选择2n个点,将这些点成对连接起来,使得所得到的n条线段不相交的方法数。
3.将多边行划分为三角形问题。
将一个凸多边形区域分成三角形区域的方法数?
类似:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果她
从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?
4.给顶节点组成二叉树的问题。
给定N个节点,能构成多少种形状不同的二叉树?
(一定是二叉树!
先去一个点作为顶点,然后左边依次可以取0至N-1个相对应的,右边是N-1到0个,两两配对相乘,就是h(0)*h(n-1) + h(2)*h(n-2) + ... + h(n-1)h(0)=h(n))
(能构成h(N)个)
以前写过的一个大整数catlan数计算程序:
#include <iostream>
#include <string>
#include <iomanip>
#include <memory.h>
const long MaxLength=100;//数字最大位数是MaxLength *4
using namespace std;
class BigInt
{
public:
long *a;
long length;
long max;
BigInt()//构造函数
{
a = new long [MaxLength];
memset(a,0,sizeof(long)*MaxLength);
length = 1;
max = MaxLength;
}
BigInt(const BigInt&t)
{
long *a = new long [t.length];
for(int i=0;i<t.length;i++)
a[i] = t.a[i];
length = t.length;
max = t.max;
}
~BigInt()//析构函数
{
delete [] a;
}
void clear()//清零函数
{
memset(a,0,sizeof(long)*max);
length = 1;
}
friend ostream &operator << (ostream & output,BigInt &t)
{
output << t.a[t.length-1];
for(int i=t.length-2;i>=0;i--)
output << setw(4) << setiosflags(ios::right) << setfill('0') << t.a[i];
return output;
}
void operator = (const BigInt & t)
{
if(t.a==a)
return;
delete a;
a = new long [t.length];
for(int i=0;i<t.length;i++)
a[i] = t.a[i];
length = t.length;
}
void operator = (const string & temp)
{
int tLength = temp.length() - 1;
length=tLength/4+1;
for(int i=0;tLength>2;i++,tLength-=4)
a[i]=(temp[tLength]-'0')+(temp[tLength-1]-'0')*10+(temp[tLength-2]-'0')*100+(temp[tLength-3]-'0')*1000;
if(tLength==2)
a[length-1]=(temp[tLength]-'0')+(temp[tLength-1]-'0')*10+(temp[tLength-2]-'0')*100;
else if(tLength==1)
a[length-1]=(temp[tLength]-'0')+(temp[tLength-1]-'0')*10;
else if(!tLength)
a[length-1]=(temp[tLength]-'0');
}
BigInt operator + (const BigInt & t)
{
BigInt sum;
for(int i=0;i<t.length||i<length;i++)
{
sum.a[i]+=a[i]+t.a[i];
if(sum.a[i]>9999)
{
int j=i;
while(sum.a[j]>9999)
{
sum.a[j+1]+=sum.a[j]/10000;
sum.a[j]%=10000;
j++;
}
}
}
for(int i=sum.max-1;i>=sum.length;i--)
if(sum.a[i])
{
sum.length=i+1;
break;
}
return sum;
}
BigInt operator * (const BigInt & t)
{
BigInt product;
for(int i=0;i<t.length;i++)
for(int j=0;j<length;j++)
{
product.a[i+j]+=a[j]*t.a[i];
if(product.a[i+j]>9999)
{
int q=i+j;
while(product.a[q]>9999)
{
product.a[q+1]+=product.a[q]/10000;
product.a[q]%=10000;
q++;
}
}
}
for(int i=product.max-1;i>=product.length;i--)
if(product.a[i])
{
product.length=i+1;
break;
}
return product;
}
};
BigInt s[MaxLength+1];
int main()
{
int n;
s[0]= "1";
s[1]="1";s[2]="2";s[3]="5";
for(int i=4;i<101;i++)
for(int j=0;j<i;j++)
// s[i] = add(s[i],mult(s[j],s[i-j]));
s[i] = s[i]+s[j]*s[i-j-1];
while(cin>>n && n != -1)
{
cout<<s[n]<<endl;
}
return 0;
}