HDOJ 1023 Train Problem II (卡塔兰数)

 

Problem 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.
 
解析:

卡塔兰数

h(1)=1;

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

(位数很大,要用到大数乘法和除法)

#include<iostream> 
#include<cstring>
using namespace std;
int wide=10000;//类似以10000为进制 

int main()
{
    int a[105][20];
    memset(a,0,sizeof(a));        //必要 
    a[1][1]=1;
    
    for(int i=2;i<=100;i++)
        {
            int c=0;
             for(int j=1;j<20;j++)
                 {
                     int t=a[i-1][j]*(4*i-2)+c;
                     a[i][j]=t%wide;// 只要结果不超过19位就不用考虑最高进位,否则加while(c){%.\.++};
                     c=t/wide;      //(另外可以用a[i][0]存储长度)
                }                  //保留的是余数,进的是商 
                                                   
            c=0;                   
            for(int j=19;j>=1;j--)
                {                   //除法与乘法刚好相反
                    int t=a[i][j]+c*wide;// 1.从最高位开始除;2.i余数乘进制 ii再除以除数 
                    a[i][j]=t/(i+1);     //保留的是商,进的是余数 
                    c=t%(i+1);           //不用考虑最低进位,虽然我也不懂为啥。 
                }

        }
    int n;
    while(cin>>n)
        {
            int flag=0;
            for(int i=19;i>0;i--)        //逆向输出 
                {
                    if(flag)             //前面的0不用输出,出现的第一个数直接输出,
                        printf("%04d",a[n][i]);//后面的数都要以进制位数输出 
                     else if(a[n][i]!=0)        //else if 的使用很巧妙 
                         {
                             printf("%d",a[n][i]);
                             flag=1;
                        }
                }
            /*if(a[n][0]!=0)
                printf("%04d",&a[n][0]); */
            printf("\n");
        }
    return 0;
}

 

 

拓展

 

卡塔兰数

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

(18141894)命名。

卡塔兰数的一般项公式为 

前几项为 (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, ...

性质

Cn的另一个表达形式为 所以,Cn是一个自然数;这一点

在先前的通项公式中并不显而易见。这个表达形式也是André对前一公式证明的基础。

递推关系

 

它也满足

//这个在本题中用来推导卡特兰数!

很重要的一个推导式!!

   这提供了一个更快速的方法来计算卡塔兰数。

   卡塔兰数的渐近增长为

 

它的含义是左式除以右式的商趋向于1当n → ∞。(这可以用n!的斯特灵公式来证明。)

所有的奇卡塔兰数Cn都满足。所有其他的卡塔兰数都是偶数。

 

卡特兰数的应用:

出栈次序

  一个(无穷大)的进栈序列为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)=1。

  此处f(2)=1和f(3)=1的具体缘由须参考详尽的“卡特兰数”,也许可从

凸四边形f(4)=f(2)f(3)+ f(3)f(2)=2×f(2)f(3)倒推,四边形的

划分方案不用规律推导都可以知道是2,那么

2×f(2)f(3)=2,则f(2)f(3)=1,又f(2)和f(3)若存在的话一定是整数,

则f(2)=1,f(3)=1。

(因为我没研究过卡特兰数的由来,此处仅作刘抟羽的臆测)。

 …………

 

2.在c以及c++中,EOF被定义成-1!

3.如何处理大数的乘除法!很妙的方法!参考代码!

代码如下:

//输出卡特兰数

//首先需要肯定,程序是正确的

//这算是大数乘除法!记住他们是如何处理的!由于数据很大,用基本数据类型根本无法满足要求,只能用数组来表示!

#include <iostream>

#include<cstdio>

#include<memory.h>

using namespace std;

#define MAX 101

#define BASE 10000//base只是一个基度,对最终取值并没有影响,相反,base取值愈大,计算量愈小

//base发生改变的时候,下面的输出也要相应地做出调整,否则也会输出错误答案!除非当base取10!

void multiply(int a[],int len,int b)//乘法

{

    for(int i=len-1,carry=0;i>=0;--i)//从最后一位开始相乘,依次向前与每一位相乘

    {//问题在于,为什么BASE=10000?

        carry+=b*a[i];

        a[i]=carry%BASE;

        carry/=BASE;

    //cout<<"carry="<<carry<<" "<<"a["<<i<<"]="<<a[i]<<endl;//以4个0为一组

    }

}

void divide(int a[],int len,int b)//除法,很妙的!这种除法可能想不到,仔细体会!

{//应当如何除呢?

    for(int i=0,div=0;i<len;++i)//从高位除起

    {

        div=div*BASE+a[i];

        a[i]=div/b;//b为除数

        div%=b;

    }

}

int main()

{

    int i,j,h[101][MAX];

    memset(h[1],0,MAX*sizeof(int));//赋值,每一个都置为0

    for(i=2,h[1][MAX-1]=1;i<=100;++i)//运用递归,并且h[1]=1;

    {

        memcpy(h[i],h[i-1],MAX*sizeof(int));//h[i]=h[i-1];按字节拷贝,保证了h[i]和h[i-1]指向数组的一致性

        multiply(h[i],MAX,4*i-2);//h[i]*=(4*i-2);

        divide(h[i],MAX,i+1);//h[i]/=(i+1);       

    }//递归得到前100项的卡特兰数!

    while(cin>>i && i>=1 && i<=100)//输入i的值

    {

                // for(i=1;i<=100;i++)

                // {

        for(j=0;j<MAX && h[i][j]==0;++j);//从0位开始搜索,找到不为0的第一个数

                   //printf("%d\n",EOF);在c语言中,EOF=-1;

                   printf("%d",h[i][j++]);//像是这个输出,就很妙了,第一位可能不足四位,就地输出!

              for(;j<MAX;++j)

                   {

                   //     if(h[i][j]==0)

                   printf("%04d",h[i][j]);//处在中间的值也可能没有四位,这时候要注意了,往左边加0,凑足4位,不然答案会出错!

                            //     else

         // printf("%d",h[i][j]);//不断输出值

       

       }

   

             printf("\n");

         }

    system("pause");

        

    return 0;

}

 

 

 

 

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

//*******************************

//打表卡特兰数

//第 n个 卡特兰数存在a[n]中,a[n][0]表示长度;

//注意数是倒着存的,个位是 a[n][1] 输出时注意倒过来。

//*********************************

 

#include<stdio.h>

int a[105][100];

void ktl()

{

    int i,j,yu,len;

    a[2][0]=1;

    a[2][1]=2;

    a[1][0]=1;

    a[1][1]=1;

    len=1;

    for(i=3;i<101;i++)

    {

        yu=0;

        for(j=1;j<=len;j++)

        {

            int t=(a[i-1][j])*(4*i-2)+yu;

            yu=t/10;

            a[i][j]=t%10;

        }   

        while(yu)

        {

            a[i][++len]=yu%10;

            yu/=10;

        }

        for(j=len;j>=1;j--)

        {

            int t=a[i][j]+yu*10;

            a[i][j]=t/(i+1);

            yu = t%(i+1);

        }       

        while(!a[i][len])

        {

            len--;

        }   

        a[i][0]=len;

    }   

   

}   

int main()

{

    ktl();

    int n;

    while(scanf("%d",&n)!=EOF)

    {

        for(int i=a[n][0];i>0;i--)

        {

            printf("%d",a[n][i]);

        }   

        puts("");

    }   

    return 0;

}

 
posted on 2017-09-02 10:39  bigganbing  阅读(183)  评论(0编辑  收藏  举报