ACM數論-卡特蘭數Catalan


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位二進制數共有{2n \choose n}個,下面考慮不滿足要求的數目.

 

考慮一個含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的二進制數。反之亦然(相似的思路證明兩者一一對應)。

 

從而C_n = {2n \choose n} - {2n \choose n + 1} = \frac{1}{n+1}{2n \choose n}

 

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的累計數的方案種數。

4、給頂節點組成二叉樹的問題。
  給定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中選擇方法,所以可得總的構造方法數目如式。

 

  (一定是二叉樹!先取一個點作為頂點,然后左邊依次可以取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)個)

       在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 }
Catalan 高精度

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM