Catalan
原理:
令h(0)=1,h(1)=1,catalan 數滿足遞歸式:
(其中n>=2)
另類遞推公式:
該遞推關系的解為:
(n=1,2,3,...)
卡特蘭數的應用實質上都是遞歸等式的應用
前幾項為: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, ...
應用:
問題描述:
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)。
在<<計算機程序設計藝術>>,第三版,Donald E.Knuth著,蘇運霖譯,第一卷,508頁,給出了證明:
問題大意是用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)。
c(2n, n)/(n+1) = c(2n, n) - c(2n, n-1)
證明:
令1表示進棧,0表示出棧,則可轉化為求一個2n位、含n個1、n個0的二進制數,滿足從左往右掃描到任意一位時,經過的0數不多於1數。顯然含n個1、n個0的2n位二進制數共有個,下面考慮不滿足要求的數目.
考慮一個含n個1、n個0的2n位二進制數,掃描到第2m+1位上時有m+1個0和m個1(容易證明一定存在這樣的情況),則后面的0-1排列中必有n-m個1和n-m-1個0。將2m+2及其以后的部分0變成1、1變成0,則對應一個n+1個0和n-1個1的二進制數。反之亦然(相似的思路證明兩者一一對應)。
從而。
Catalan 典型應用:
1、括號化問題。矩陣鏈乘: P=A1×A2×A3×……×An,依據乘法結合律,不改變其順序,只用括號表示成對的乘積,試問有幾種括號化的方案?
一個有n個X和n個Y組成的字串,且所有的部分字串皆滿足X的個數大於等於Y的個數。以下為長度為6的dyck words:
XXXYYY XYXXYY XYXYXY XXYYXY XXYXYY
將上例的X換成左括號,Y換成右括號,Cn表示所有包含n組括號的合法運算式的個數:
((())) ()(()) ()()() (())() (()())
2、將多邊行划分為三角形問題。將一個凸多邊形區域分成三角形區域(划分線不交叉)的方法數?
一個凸多邊形區域,有N條邊,將其划分為三角形區域,問共有多少種分割方法。
(1)我們從最簡單情況開始:N=3,f(3)=1;
(2)當N=4,f(4)=2;
(3)N邊時
我們從節點1開始考慮,要想分割成三角形區域,1不能和與它相鄰的點連接,所以1可以 連接3,4,...,N-1;
假設1連接i,則分割成的兩個區域分別為i凸多邊形和N+2-i凸多邊形,即對於節點1,f1(N)=f(3)f(N+2-3)+f(4)f(N+2-4)+...+f(N-1)f(3);
N多邊形共N個點,對應於每個點有f1(N)中分割方法,總的分割方法為f(N)=Nf1(N),但是每增加一條邊,其連接兩個點,所以在f(N)中有
一半是重復情況,所以最終的分割方法為:
類似:在圓上選擇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的累計數的方案種數。
給定N個節點,能構成多少種形狀不同的二叉樹?
(1)n=0時,f(0)=1;
(2)n=1時,f(1)=1;
(3)n=2時,f(2)=4;
(4) f(N)=N(f(0)f(N-1)+f(1)f(N-2)+...+f(i)f(N-1-i)+...+f(N-1)f(0));采用遞歸的思想,f(i)f(N-1-i)中f(i)表示左子樹有i個節點可構造的二叉樹數目,f(N-1-i)表示右子樹有N-1-i個節點可構造的二叉樹數目;
兩者相乘表示左右分別為i,N-1-i個節點時候這個大的二叉樹的構造數目,又由於根節點的選擇有N中選擇方法,所以可得總的構造方法數目如式。

在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 //大數&&Catalan 2 /* 3 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 4 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 5 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 6 4861946401452, … 7 */ 8 #include<iostream> 9 #include<cstring> 10 #include<iomanip> 11 #include<algorithm> 12 using namespace std; 13 14 #define MAXN 9999 15 #define MAXSIZE 10 16 #define DLEN 4 17 18 class BigNum 19 { 20 private: 21 int a[500]; //可以控制大數的位數 22 int len; //大數長度 23 public: 24 BigNum(){ len = 1;memset(a,0,sizeof(a)); } //構造函數 25 BigNum(const int); //將一個int類型的變量轉化為大數 26 BigNum(const char*); //將一個字符串類型的變量轉化為大數 27 BigNum(const BigNum &); //拷貝構造函數 28 BigNum &operator=(const BigNum &); //重載賦值運算符,大數之間進行賦值運算 29 30 friend istream& operator>>(istream&, BigNum&); //重載輸入運算符 31 friend ostream& operator<<(ostream&, BigNum&); //重載輸出運算符 32 33 BigNum operator+(const BigNum &) const; //重載加法運算符,兩個大數之間的相加運算 34 BigNum operator-(const BigNum &) const; //重載減法運算符,兩個大數之間的相減運算 35 BigNum operator*(const BigNum &) const; //重載乘法運算符,兩個大數之間的相乘運算 36 BigNum operator/(const int &) const; //重載除法運算符,大數對一個整數進行相除運算 37 38 BigNum operator^(const int &) const; //大數的n次方運算 39 int operator%(const int &) const; //大數對一個int類型的變量進行取模運算 40 bool operator>(const BigNum & T)const; //大數和另一個大數的大小比較 41 bool operator>(const int & t)const; //大數和一個int類型的變量的大小比較 42 43 void print(); //輸出大數 44 }; 45 BigNum::BigNum(const int b) //將一個int類型的變量轉化為大數 46 { 47 int c,d = b; 48 len = 0; 49 memset(a,0,sizeof(a)); 50 while(d > MAXN) 51 { 52 c = d - (d / (MAXN + 1)) * (MAXN + 1); 53 d = d / (MAXN + 1); 54 a[len++] = c; 55 } 56 a[len++] = d; 57 } 58 BigNum::BigNum(const char*s) //將一個字符串類型的變量轉化為大數 59 { 60 int t,k,index,l,i; 61 memset(a,0,sizeof(a)); 62 l=strlen(s); 63 len=l/DLEN; 64 if(l%DLEN) 65 len++; 66 index=0; 67 for(i=l-1;i>=0;i-=DLEN) 68 { 69 t=0; 70 k=i-DLEN+1; 71 if(k<0) 72 k=0; 73 for(int j=k;j<=i;j++) 74 t=t*10+s[j]-'0'; 75 a[index++]=t; 76 } 77 } 78 BigNum::BigNum(const BigNum & T) : len(T.len) //拷貝構造函數 79 { 80 int i; 81 memset(a,0,sizeof(a)); 82 for(i = 0 ; i < len ; i++) 83 a[i] = T.a[i]; 84 } 85 BigNum & BigNum::operator=(const BigNum & n) //重載賦值運算符,大數之間進行賦值運算 86 { 87 int i; 88 len = n.len; 89 memset(a,0,sizeof(a)); 90 for(i = 0 ; i < len ; i++) 91 a[i] = n.a[i]; 92 return *this; 93 } 94 istream& operator>>(istream & in, BigNum & b) //重載輸入運算符 95 { 96 char ch[MAXSIZE*4]; 97 int i = -1; 98 in>>ch; 99 int l=strlen(ch); 100 int count=0,sum=0; 101 for(i=l-1;i>=0;) 102 { 103 sum = 0; 104 int t=1; 105 for(int j=0;j<4&&i>=0;j++,i--,t*=10) 106 { 107 sum+=(ch[i]-'0')*t; 108 } 109 b.a[count]=sum; 110 count++; 111 } 112 b.len =count++; 113 return in; 114 115 } 116 ostream& operator<<(ostream& out, BigNum& b) //重載輸出運算符 117 { 118 int i; 119 cout << b.a[b.len - 1]; 120 for(i = b.len - 2 ; i >= 0 ; i--) 121 { 122 cout.width(DLEN); 123 cout.fill('0'); 124 cout << b.a[i]; 125 } 126 return out; 127 } 128 129 BigNum BigNum::operator+(const BigNum & T) const //兩個大數之間的相加運算 130 { 131 BigNum t(*this); 132 int i,big; //位數 133 big = T.len > len ? T.len : len; 134 for(i = 0 ; i < big ; i++) 135 { 136 t.a[i] +=T.a[i]; 137 if(t.a[i] > MAXN) 138 { 139 t.a[i + 1]++; 140 t.a[i] -=MAXN+1; 141 } 142 } 143 if(t.a[big] != 0) 144 t.len = big + 1; 145 else 146 t.len = big; 147 return t; 148 } 149 BigNum BigNum::operator-(const BigNum & T) const //兩個大數之間的相減運算 150 { 151 int i,j,big; 152 bool flag; 153 BigNum t1,t2; 154 if(*this>T) 155 { 156 t1=*this; 157 t2=T; 158 flag=0; 159 } 160 else 161 { 162 t1=T; 163 t2=*this; 164 flag=1; 165 } 166 big=t1.len; 167 for(i = 0 ; i < big ; i++) 168 { 169 if(t1.a[i] < t2.a[i]) 170 { 171 j = i + 1; 172 while(t1.a[j] == 0) 173 j++; 174 t1.a[j--]--; 175 while(j > i) 176 t1.a[j--] += MAXN; 177 t1.a[i] += MAXN + 1 - t2.a[i]; 178 } 179 else 180 t1.a[i] -= t2.a[i]; 181 } 182 t1.len = big; 183 while(t1.a[len - 1] == 0 && t1.len > 1) 184 { 185 t1.len--; 186 big--; 187 } 188 if(flag) 189 t1.a[big-1]=0-t1.a[big-1]; 190 return t1; 191 } 192 193 BigNum BigNum::operator*(const BigNum & T) const //兩個大數之間的相乘運算 194 { 195 BigNum ret; 196 int i,j,up; 197 int temp,temp1; 198 for(i = 0 ; i < len ; i++) 199 { 200 up = 0; 201 for(j = 0 ; j < T.len ; j++) 202 { 203 temp = a[i] * T.a[j] + ret.a[i + j] + up; 204 if(temp > MAXN) 205 { 206 temp1 = temp - temp / (MAXN + 1) * (MAXN + 1); 207 up = temp / (MAXN + 1); 208 ret.a[i + j] = temp1; 209 } 210 else 211 { 212 up = 0; 213 ret.a[i + j] = temp; 214 } 215 } 216 if(up != 0) 217 ret.a[i + j] = up; 218 } 219 ret.len = i + j; 220 while(ret.a[ret.len - 1] == 0 && ret.len > 1) 221 ret.len--; 222 return ret; 223 } 224 BigNum BigNum::operator/(const int & b) const //大數對一個整數進行相除運算 225 { 226 BigNum ret; 227 int i,down = 0; 228 for(i = len - 1 ; i >= 0 ; i--) 229 { 230 ret.a[i] = (a[i] + down * (MAXN + 1)) / b; 231 down = a[i] + down * (MAXN + 1) - ret.a[i] * b; 232 } 233 ret.len = len; 234 while(ret.a[ret.len - 1] == 0 && ret.len > 1) 235 ret.len--; 236 return ret; 237 } 238 int BigNum::operator %(const int & b) const //大數對一個int類型的變量進行取模運算 239 { 240 int i,d=0; 241 for (i = len-1; i>=0; i--) 242 { 243 d = ((d * (MAXN+1))% b + a[i])% b; 244 } 245 return d; 246 } 247 BigNum BigNum::operator^(const int & n) const //大數的n次方運算 248 { 249 BigNum t,ret(1); 250 int i; 251 if(n<0) 252 exit(-1); 253 if(n==0) 254 return 1; 255 if(n==1) 256 return *this; 257 int m=n; 258 while(m>1) 259 { 260 t=*this; 261 for( i=1;i<<1<=m;i<<=1) 262 { 263 t=t*t; 264 } 265 m-=i; 266 ret=ret*t; 267 if(m==1) 268 ret=ret*(*this); 269 } 270 return ret; 271 } 272 bool BigNum::operator>(const BigNum & T) const //大數和另一個大數的大小比較 273 { 274 int ln; 275 if(len > T.len) 276 return true; 277 else if(len == T.len) 278 { 279 ln = len - 1; 280 while(a[ln] == T.a[ln] && ln >= 0) 281 ln--; 282 if(ln >= 0 && a[ln] > T.a[ln]) 283 return true; 284 else 285 return false; 286 } 287 else 288 return false; 289 } 290 bool BigNum::operator >(const int & t) const //大數和一個int類型的變量的大小比較 291 { 292 BigNum b(t); 293 return *this>b; 294 } 295 296 void BigNum::print() //輸出大數 297 { 298 int i; 299 cout << a[len - 1]; 300 for(i = len - 2 ; i >= 0 ; i--) 301 { 302 cout.width(DLEN); 303 cout.fill('0'); 304 cout << a[i]; 305 } 306 cout << endl; 307 } 308 int main() 309 { 310 int i,n; 311 BigNum x[101]; //定義大數的對象數組 312 x[0]=1; 313 for(i=1;i<101;i++) 314 x[i]=x[i-1]*(4*i-2)/(i+1); 315 while(scanf("%d",&n)==1 && n!=-1) 316 { 317 x[n].print(); 318 } 319 }