卡特蘭數(Catalan)

本文轉載http://m.blog.csdn.net/mobius_strip/article/details/39229895

也可參見百度百科


一、介紹

        卡塔蘭數是組合數學中一個常在各種計數問題中出現的數列。以比利時的數學家歐仁·查理·卡塔蘭18141894)命名。

        歷史上,清代數學家明安圖(1692年-1763年)在其《割圜密率捷法》最早用到“卡塔蘭數”。

        卡特蘭序列的前11項爲:1, 1, 2, 5,14, 42, 132, 429, 1430, 4862, 16796。

二、性質

2.1 通項公式

        卡塔蘭數的一般項公式爲 
        C_n = \frac{1}{n+1}{2n \choose n} = \frac{(2n)!}{(n+1)!n!} 

        Cn的另一個表達形式爲 :

        C_n = {2n\choose n} - {2n\choose n+1} \quad\mbox{ for }n\ge 1  

        所以,Cn是一個自然數;這一點在先前的通項公式中並不顯而易見。這個表達形式也是André對前一公式證明的基礎。

2.2 遞推關係

        a.C_0 = 1 \quad \mbox{and} \quad C_{n+1}=\sum_{i=0}^{n}C_i\,C_{n-i}\quad\mbox{for }n\ge 0.

        b.C_0 = 1 \quad \mbox{and} \quad C_{n+1}=\frac{2(2n+1)}{n+2}C_n,

        C(n)=C(n-1)*(4*n-2)/(n+1);(常用)

        這提供了一個更快速的方法來計算卡塔蘭數。卡塔蘭數的漸近增長爲:

         C_n \sim \frac{4^n}{n^{3/2}\sqrt{\pi}}
        它的含義是當n → ∞時,左式除以右式的商趨向於1。(這可以用n!斯特靈公式來證明。

        c. 所有的奇卡塔蘭數Cn都滿足所有其他的卡塔蘭數都是偶數。

        d. 當n > 4 是,Cn 不是素數。{Cn | (2n)!  推出  Cn若是素數則Cn < 2n  推出  n <4 }。

三、等價問題

        這裏列舉一些卡塔蘭數的等價形式。(邊界條件可能不同)

        (1)給定n個元素,依次通過一個棧,求可能的出棧序列的個數。

        解:
        對於每一個數來說,必須進棧一次、出棧一次。我們把進棧設爲狀態‘1’,出棧設爲狀態‘0’。n個數的所有狀態對應n個1和n個0組成的2n位二進制數。由於任意時刻,出棧的操作數一定不超過入棧的操作數,因此輸出序列的總數目爲由左而右掃描由n個1和n個0組成的2n位二進制數中,1的累計數不小於0的累計數的方案種數(可以應用卡特蘭數)

        在2n位二進制數中填入n個1的方案數爲c(2n,n)。從中減去不符合要求(由左而右掃描,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)(常用)

        類似問題 買票找零

有2n個人排成一行進入劇場。入場費5元。其中只有n個人有一張5元鈔票,另外n人只有10元鈔票,劇院無其它鈔票,問有多少中方法使得只要有10元的人買票,售票處就有5元的鈔票找零?(將持5元者到達視作將5元入棧,持10元者到達視作使棧中某5元出棧)

        (2)n個左括號和n個右括號組成的合法匹配的字符串的個數。

        解:這個題目只要將上面證明過程中的1對應左括號,0對應右括號即可。

        (3)n+1個數字連乘,不同的計算順序個數。

        解:這個就相當於加入了n對合法的括號。

        (4)Cn表示長度2ndyck word的個數。

       (Dyck word是一個有nXnY組成的字串,且所有的部分字串皆滿足X的個數大於等於Y的個數。)

        解:(1)中X對應1,Y對應0,即可。

        (5)n個節點的二叉樹的可能形態的種類數。

        解:(2)中的每個不同的括號串,一一映射於二叉樹的每一種情況。

        這裏利用另一個通項公式推導一下:

        設n個節點的二叉樹有F(n)個,則分爲根的左子樹0各節點、右子樹n-1個節點,跟的左子樹1個節點、右子樹n-2個節點,...:

        F(n)= F(0)*F(n-1)+ F(1)*F(n-2)+ ... + F(n-1)*F(0)= Cn。

        (6)n個非葉節點的滿二叉樹的形態數。(對稱後得到的二叉樹除非自己本身對稱,否則算是不同)

       

        設n個節點的二叉樹有F(n)個,則分爲根1爲根,二爲根,...,n爲根:

        F(n)= F(0)*F(n-1)+ F(1)*F(n-2)+ ... + F(n-1)*F(0)= Cn(卡特蘭數一般公式)

        (7)對於一個n*n的網格,每次我們能向右或者向上移動一格,那麼從左下角到右上角的所有在副對角線下方的路徑數。

           

        解:我們將一條水平邊記爲1,垂直邊記爲0,那麼就組成了一個n個,1和n個0的序列,同(1)。

        (8)n+2邊形進行三角形分割數。(只連接頂點對形成n個三角形)

          

         解:將節點按順時針辦好1~n,枚舉1和其他所有點的連線情況,則:

         F(n)= Σ(F(k)F(n+2-k)){2< k < n};解得 F(n) = Cn-2。

        (9)圓周上2n-2個點的不相交連弦方式。(凸2n-2多邊形的頂點也成立)

         解:設Bn爲2n-2個點的不相交連弦方式數,則任取一點A有n-1中連弦方式(兩側必須是偶數個點)

         兩側分別是2(k-1)和2(n-k-1)個點,分別爲Bk和Bn-k種方式,因此Bn = Cn-1。

        (10)對於集合的不交叉劃分的數目。

         對於集合{a,b}{c,d},假設他們組成了兩個區間[a,b][c,d],我們假設兩個區間不重合,那麼以下四種情況當做是不交叉的:a<c<d<ba<b<c<dc<a<b<dc<d<a<b,就是說兩個區間可以包含或者相離,那麼此時我們稱集合{a,b}{c,d}是不交叉的。對於集合,將裏面元素兩兩分爲一子集,共n個,若任意兩個子集都是不交叉的,那麼我們稱此時的這個劃分爲一個不交叉劃分。

        解:我們將每個子集中較小的數用左括號代替,較大的用右括號代替,那麼就是(2)。

       (11)n層的階梯切割爲n個矩形的切法數。

        

        解:我們先繪製如下的一張圖片,即n爲5的時候的階梯:

         

        我們注意到每個切割出來的矩形都必需包括一塊標示爲*的小正方形,那麼我們此時枚舉每個*#標示的兩角作爲矩形;

        剩下的兩個小階梯就是我們的兩個更小的子問題了,於是我們的注意到這裏的式子就是Cn的遞推公式了

       (12)在一個2*n的格子中填入12n使得每個格子內的數值都比其右邊和上邊的所有數值都小的情況數。

        解:可以轉化成括號匹配問題。

       (13)2n個不同實數分成兩組A = {a1,a2...,an},B= {b1,b2...,bn},使得對應的數字均有ai<bi的拆分數。

        解:和上面問題等價。

       (14)2n-2張選票,投給甲乙,甲任意時刻得票多於乙的情況數。

        解:即01串的入棧出棧證明。

       (15)不定方程 Σxi = n-1(1到n-1),滿足Σ xi ≥ k (1到k)的解的個數。

        解:同上。

四、具體題目

4.1 UVa 991 - Safe Salutations 

求圓內不相交弦的連法數

#include <iostream>
#include <cstdlib>
#include <cstdio>

using namespace std;

int f[22][22] = {0};

int main()
{
	for (int i = 0 ; i <= 20 ; ++ i)
		f[i][0] = f[i][i] = 1;
	for (int i = 1 ; i <= 20 ; ++ i)
	for (int j = 1 ; j < i ; ++ j)
		f[i][j] = f[i-1][j]+f[i-1][j-1];
		
	int n,t = 0;
	while (cin >> n) {
		if (t ++) cout << endl;
		cout << f[2*n][n]/(n+1) << endl;
	}
	return 0;
}
4.2 UVa 10303 - How Many Trees? 

統計BST個數

#include <iostream>
#include <cstdlib>
#include <cstdio>

using namespace std;

int C[1005][1005] = {0};

int main()
{
	C[1][0] = 1;
	for (int i = 2 ; i < 1001 ; ++ i) {
		for (int j = 0 ; j < 1000 ; ++ j)
			C[i][j] += C[i-1][j]*(4*i-2);
		for (int j = 0 ; j < 1000 ; ++ j) {
			C[i][j+1] += C[i][j]/10;
			C[i][j] %= 10;
		}
		for (int j = 999 ; j >= 0 ; -- j) {
			C[i][j-1] += C[i][j]%(i+1)*10;
			C[i][j] /= (i+1);
		}
	}
		
	int n;
	while (cin >> n) {
		int end = 999;
		while (!C[n][end]) -- end;
		while (end >= 0) printf("%d",C[n][end --]);
		printf("\n");
	}
	return 0;
}
4.3 UVa 10007 - Count the Trees 

統計二叉樹的個數,由於求的不是樹的形狀數,而是所有樹的個數,所以本題要乘以n!

#include <iostream>
#include <cstdlib>
#include <cstdio>

using namespace std;

int C[305][2005] = {0};

int main()
{
	C[1][0] = 1;
	for (int i = 2 ; i < 301 ; ++ i) {
		for (int j = 0 ; j < 2000 ; ++ j)
			C[i][j] += C[i-1][j]*(4*i-2);
		for (int j = 0 ; j < 2000 ; ++ j) {
			C[i][j+1] += C[i][j]/10;
			C[i][j] %= 10;
		}
		for (int j = 1999 ; j >= 0 ; -- j) {
			C[i][j-1] += C[i][j]%(i+1)*10;
			C[i][j] /= (i+1);
		}
		
		for (int j = 0 ; j < 2000 ; ++ j)
			C[i][j] *= i;
		for (int j = 0 ; j < 2000 ; ++ j) {
			C[i][j+1] += C[i][j]/10;
			C[i][j] %= 10;
		}
	}
		
	int n;
	while (cin >> n && n) {
		int end = 1999;
		while (!C[n][end]) -- end;
		while (end >= 0) printf("%d",C[n][end --]);
		printf("\n");
	}
	return 0;
}
4.4 UVa 1478 - Delta Wave

求從(0,0)到(N,0)的路徑數,每次可以斜向上或者斜向下或者直走。

因爲不能走到負的區域,所以上升的和下降的此時必然相等,而且上升次數要隨時不小於下降次數。

由此可知,上升和下降是上面提到的括號合法匹配,枚舉所有的上升下降次數有:

邊長爲n的圖中走法數 F(n)= Σ(C(n,2*i)*Ci),其中:

C(n,2*i)是n條路中有i條上升和i條下降的方案數,Ci是i條上升和i條下降的合法組合數(內部)(卡特蘭數)

化簡:F(n)= Σ(C(n,2*k)*C(2k,k)/(k+1));

設:    B(k)= C(n,2*k)*C(2k,k)/(k+1)

            得遞推關係:B(k)= B(k-1)*(n-2*k+2)*(n-2*k+1)/(k*(k+1))

利用上面遞推關係計算即可。(貌似java的大數用起來比較快╮(╯▽╰)╭)

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>

using namespace std;

int C[4808] = {0};
int ans[4808];

int main()
{
	int n;
	while (~scanf("%d",&n) && n) {
		memset(C, 0, sizeof(C));
		memset(ans, 0, sizeof(ans));
		C[0] = 1;ans[0] = 1;
		for (int i = 1 ; 2*i <= n ; ++ i) {
			for (int j = 0 ; j < 4800 ; ++ j)
				C[j] *= (n-2*i+2)*(n-2*i+1);
			for (int j = 0 ; j < 4800 ; ++ j) {
				C[j+1] += C[j]/10;
				C[j] %= 10;
			}
			for (int j = 4799 ; j >= 0 ; -- j) {
				C[j-1] += C[j]%((i+1)*i)*10;
				C[j] /= ((i+1)*i);
			}
			for (int j = 0 ; j < 4800 ; ++ j)
				ans[j] += C[j];
			for (int j = 0 ; j < 4800 ; ++ j) {
				ans[j+1] += ans[j]/10;
				ans[j] %= 10;
			}
		}
		int end = 99;
		while (!ans[end]) -- end;
		while (end >= 0) printf("%d",ans[end --]);
		printf("\n");
	}
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章