Description

As we all know the Train Problem I, the boss of the Ignatius Train Station want to know if all the trains come in strict-increasing order, how many orders that all the trains can get out of the railway.
 

Input

The input contains several test cases. Each test cases consists of a number N(1<=N<=100). The input is terminated by the end of file.
 

Output

For each test case, you should output how many ways that all the trains can get out of the railway.
 

Sample Input

1 2 3 10
 

Sample Output

1 2 5 16796

Hint

The result will be very large, so you may not process it by 32-bit integers.
 
卡特兰数又称卡塔兰数,其前几项为 : 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, ...

1.  卡塔兰数的一般项公式为 

2.  令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=0,1,2,...)
3.  另类递推式:h(n)=h(n-1)*(4*n-2)/(n+1);     所以,hn是一个自然数
     递推关系的另类解为:h(n)=c(2n,n)-c(2n,n+1)(n=0,1,2,...)

4、/begin{displaymath}C_n= /frac 1{n+1} /sum_{i=0}^n {n /choose i}^2/end{displaymath}

       5、卡塔兰数的渐近增长为/begin{displaymath}C_n /sim /frac{4^n}{n^{/frac{3}{2}}/sqrt{/pi}}/end{displaymath}它的含义是左式除以右式的商趋向于1当n → ∞。所有的奇卡塔兰数Cn都满足      n=2^k-1。所有其他的卡塔兰数都是偶数。

 

卡特兰数的应用:

 出栈次序

  一个(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?
   常规分析
   首先,我们设f(n)=序列个数为n的出栈序列种数。同时,我们假定第一个出栈的序数是k。
   第一个出栈的序数k将1~n的序列分成两个序列,其中一个是1~k-1,序列个数为k-1,另外一个是k+1~n,序列个数是n-k。
   此时,我们若把k视为确定一个序数,那么根据乘法原理,f(n)的问题就等价于——序列个数为k-1的出栈序列种数乘以序列个数为n - k的出栈
        序列种数,即选择k这个序数的f(n)=f(k-1)×f(n-k)。而k可以选1到n,所以再根据加法原理,将k取不同值的序列种数相加,得到的总序列 
       种数为:f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0)。
  看到此处,再看看卡特兰数的递推式,答案不言而喻,即为f(n)=h(n)= C(2n,n)/(n+1)= c(2n,n)-c(2n,n+1)(n=1,2,3,……)。
  最后,令f(0)=1,f(1)=1。
  非常规分析
  对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘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)=c(2n,n)/(n+1)=h(n+1)。
 
类似问题 买票找零
有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
 

凸多边形三角划分

  在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形。现在的任务是键盘上输入凸多边形的边数n,求不同划分的方案数f(n)。比如当n=6时,f(6)=14。[6]
分析如果纯粹从f(4)=2,f(5)=5,f(6)=14,……,f(n)=n慢慢去归纳,恐怕很难找到问题的递推式,我们必须从一般情况出发去找规 
             律。因为凸多边形的任意一条边必定属于某一个三角形,所以我们以某一条边为基准,以这条边的两个顶点为起点P1和终点Pn(P   
            Point),将该凸多边形的顶点依序标记为P1、P2、……、Pn,再在该凸多边形中找任意一个不属于这两个点的顶点Pk(2<=k<=n-1),来
            构成一个三角形,用这个三角形把一个凸多边形划分成两个凸多边形,其中一个凸多边形,是由P1,P2,……,Pk构成的凸k边形(顶点数即
           是边数),另一个凸多边形,是由Pk,Pk+1,……,Pn构成的凸n-k+1边形。此时,我们若把Pk视为确定一点,那么根据乘法原理,f(n)的
           问题就等价于——凸k多边形的划分方案数乘以凸n-k+1多边形的划分方案数,即选择Pk这个顶点的f(n)=f(k)×f(n-k+1)。而k可以选2到
           n-1,所以再根据加法原理,将k取不同值的划分方案相加,得到的总方案数为:f(n)=f(2)f(n-2+1)+f(3)f(n-3+1)+……+f(n-1)
          f(2)。看到此处,再看看卡特兰数的递推式,答案不言而喻,即为f(n)=h(n-1) (n=2,3,4,……)。
          最后,令f(2)=1,f(3)=11。
      类似:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果她从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?


      类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?


给顶节点组成二叉树的问题。


  给定N个节点,能构成多少种不同的二叉树?

       n个节点的二叉树的所有可能形态数为C_n,这一点很容易证明,我们考虑随便取一个节点作为根,那么他左边和右边的儿 子节点个数就确定了,假定根节点标号为x,那么左子树的标号就从1到x-1,共x-1个,右子树的标号就从x+1到n,共n-x个,那么我们的x从1取到n,就获得了所有的情况数/begin{displaymath}C_n = /sum_{i = 0}^{n - 1}C_i/,C_{n - i - 1}/end{displaymath}。这个式子就是我们性质3的式子。
   n+1个数连乘,n+1个数连乘,乘法顺序有C_n
    我们取n为3来看看,n为3的时候就是4个数相乘了,那么我们设为abcd,最初的标号定在a上,我们对于n为3得到合法的括号序列有5个,分别是:((())),()(()),()()(),(())()和(()()),那么我们将一个左括号看成是当前操作数指针往右移动一个位置,一个右括号看成是当前操作数和左边最近的一块操作数相乘起来,那么对应的五个表达式就是:a(b(cd)),(ab)(cd),((ab)c)d,(a(bc))d和a((bc)d),他们之间是一一对应关系。
  

   n个非叶节点的满二叉树的形态数(对称后得到的二叉树除非自己本身对称,否则算是不同)

这里要求满二叉树,实际上就是在上一点的每个子节点的空儿子上都加上叶子,就形成了我们的图了,那么我们要求的结果就是Catalan数。

  对于一个n*n的正方形网格,每次我们能向右或者向上移动一格,那么从左下角到右上角的所有在副对角线右下方的路径总数为C_n

我们将一条水平边记为+1,垂直边记为-1,那么就组成了一个n个+1和n个-1的序列,我们所要保证的就是前k步中水平边的个数不小于垂直边的个数,换句话说前k个元素的和非负,就是我们关于Catalan数的定义。

凸n+2边形进行三角形分割(只连接顶点对形成n个三角形)数:

对于集合/{1,2,...,2n/}的不交叉划分的数目为C_n

这里解释一下不交叉划分,我们对于集合{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}是不交叉的。对于集合/{1,2,...,2n/},将里面元素两两分为一子集,共n个,若任意两个子集都是不交叉的,那么我们称此时的这个划分为一个不交叉划分。此时不交叉的划分数就是我们的C_n了,证明也很容易,我们将每个子集中较小的数用左括号代替,较大的用右括号代替,那么带入原来的1至2n的序列中就形成了合法括号问题,就是我们第二点的结论。例如我们的集合{1,2,3,4,5,6}的不交叉划分有五个:{{1,2},{3,4},{5,6}},{{1,2},{3,6},{4,5}},{{1,4},{2,3},{5,6}},{{1,6},{2,3},{4,5}}和{{1,6},{2,5},{3,4}}。

 
n层的阶梯切割为n个矩形的切法数也是C_n如下图所示:

 这个证明是怎么进行的呢?我们先绘制如下的一张图片,即n为5的时候的阶梯:

我们注意到每个切割出来的矩形都必需包括一块标示为*的小正方形,那么我们此时枚举每个*与#标示的两角作为矩形,剩下的两个小阶梯就是我们的两个更小的子问题了,于是我们的C_5 = C_0 * C_4 + C_1 * C_3 + C_2 * C_2 + C_1 * C_3 + C_0 * C_4注意到这里的式子就是我们前面的性质3,因此这就是我们所求的结果了。

在一个2*n的格子中填入1到2n这些数值使得每个格子内的数值都比其右边和上边的所有数值都小的情况数也是C_n

平面上连接可以形成凸包的2n个点分成2个一组连成n条线段,两两线段之间不相交的情况总数是C_n

 

 

h( n ) = ( ( 4*n-2 )/( n+1 )*h( n-1 ) );

*******************************
打表卡特兰数
第 n个 卡特兰数存在a[n]中,a[n][0]表示长度;
注意数是倒着存的,个位是 a[n][1] 输出时注意倒过来。
*********************************
#include <iostream>
using namespace std;
int a[101][101];
void get()
{
    a[1][0]=1;  a[1][1]=1;
    a[2][0]=1;  a[2][1]=2;
    int i,j,sum=0,len=1;
    for(i=3;i<=100;i++)
    {
        sum=0;
        for(j=1;j<=len;j++)   //乘法运算h(n-1)*(4*n-2)
        {
            int t=(a[i-1][j])*(4*i-2)+sum;
            sum=t/10;
            a[i][j]=t%10;
        }
        while(sum)
        {
            a[i][++len]=sum%10;
            sum=sum/10;
        }
        for(j=len;j>=1;j--)     //除法运算,除以分母的n+1
        {
            int t=a[i][j]+sum*10;
            a[i][j]=t/(i+1);
            sum=t%(i+1);
        }
        while(a[i][len]==0)
            len--;
        a[i][0]=len;
    }
}
int main()
{
    get();
    int n;
    while(cin>>n)
    {
        for(int i=a[n][0];i>=1;i--)
            cout<<a[n][i];
        cout<<endl;
    }
    return 0;
}

 

 

 

 
posted on 2014-09-22 19:43  星斗万千  阅读(621)  评论(0编辑  收藏  举报