直通BAT--卡特兰数详解

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, ...

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)-c(2n,n+1)(n=0,1,2,...)。这个公式还可以更简单得化为 h(n)=C(2n,n)/(n+1)


3.高矮排列:

     12个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应的第一排的人高,问排列方式有多少种?

问题分析:
     我们先把这12个人从低到高排列,然后,选择6个人排在第一排,那么剩下的6个肯定是在第二排.用0表示对应的人在第一排,用1表示对应的人在第二排,那么含有6个0,6个1的序列,就对应一种方案。

比如:000000111111就对应着
第一排:0 1 2 3 4 5
第二排:6 7 8 9 10 11

比如:010101010101就对应着
第一排:0 2 4 6 8 10
第二排:1 3 5 7 9 11

问题转换为:这样的满足条件的01序列有多少个。

观察1的出现,我们考虑这一个出现能不能放在第二排。显然,在这个1之前出现的那些0,1对应的人要么是在这个1左边,要么是在这个1前面。而肯定要有一个0的,在这个1前面,统计在这个1之前的0和1的个数。也就是要求,0的个数大于1的个数。


总结:
     如果把0看成入栈操作,1看成出栈操作,就是说给定6个元素,合法的入栈出栈序列有多少个。这就是catalan数,这里只是用于栈,等价地描述还有,二叉树的枚举、多边形分成三角形的个数、圆括弧插入公式中的方法数,其通项是c(2n, n)/(n+1)。

证明:
     问题大意是用S表示入栈,X表示出栈,那么合法的序列有多少个(S的个数为n)?显然有c(2n, n)个含S,X各n个的序列,剩下的是计算不允许的序列数(它包含正确个数的S和X,但是违背其它条件)。
     在任何不允许的序列中,定出使得X的个数超过S的个数的第一个X的位置。然后在导致并包括这个X的之前部分序列中,以S代替所有的X并以X代表所有的S。结果是一个有(n+1)个S和(n-1)个X的序列。反过来,对一种类型的每个序列,我们都能逆转这个过程,而且找出导致它的前一种类型的不允许序列。例如XXSXSSSXXSSS必然来自SSXSXXXXXSSS。这个对应说明,不允许的序列的个数是c(2n, n-1),因此an = c(2n, n) - c(2n, n-1)。
     

验证:
     其中F表示前排,B表示后排,在枚举出前排的人之后,对应的就是后排的人了,然后再验证是不是满足后面的比前面对应的人高的要求。

#include
using namespace std;

int bit_cnt(int n)
{
     int result = 0;
     for (; n; n &= n-1, ++result);
     return result;
}

int main(void)
{
     int F[6], B[6];
     int i,j,k,state,ok,ans = 0;
     for (state = 0; state < (1 << 12); ++state)
     {
          if (bit_cnt(state) == 6)
          {
               i = j = 0;
               for (int k = 0; k < 12; ++k)
               {
                    if(state&(1<
                         F[i++] = k;
                    else
                         B[j++] = k;
               }
               ok = 1;
               for (k = 0; k < 6; ++k)
               {
                    if (B[k] < F[k])
                    {
                         ok = 0;
                         break;
                    }
               }
               ans += ok;
          }
     }
     cout << ans << endl;
     return 0;
}

结果:
     132
     c(12, 6)/7 = 12*11*10*9*8*7/(7*6*5*4*3*2) = 132

注意:c(2n, n)/(n+1) = c(2n, n) - c(2n, n-1)

4.棋子游戏

     有编号为1到n的若干个格子,从左到右排列。
     在某些格子中有一个棋子,不妨设第i格有棋子(1<=i<=k, 1<=k<=n)。
     每次一个人可以把一个棋子往左移若干步,但是不能跨越其它棋子,也要保证每个格子至多只有一个棋子。
     两个人轮流移动,移动不了的为输,问先手是不是有必胜策略。

5. 括号化问题

     一个有n个X和n个Y组成的字串,且所有的部分字串皆满足X的个数大于等于Y的个数。
          XXXYYY XYXXYY XYXYXY XXYYXY XXYXYY
     将X换成左括号,Y换成右括号,Cn表示所有包含n组括号的合法运算式的个数:
          ((())) ()(()) ()()() (())() (()())

6.多边行划分为三角形问题

     将一个凸多边形区域分成三角形区域(划分线不交叉)的方法数?

7.圆上连线问题

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

8.出栈次序问题

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

9.入场费问题

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

10.街区问题

     一位大城市的律师在他住所以北n个街区和以东n个街区处工作,每天她走2n个街区去上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?

11.二叉树构成问题

     给定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)个)

12.填数问题

     在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++)