1. 程式人生 > >卡特蘭數(catalan數)總結 (卡特蘭大數、卡特蘭大數取模、卡特蘭數應用)

卡特蘭數(catalan數)總結 (卡特蘭大數、卡特蘭大數取模、卡特蘭數應用)

本文講解卡特蘭數的各種遞推公式,以及卡特蘭數、卡特蘭大數、卡特蘭大數取模的程式碼實現,最後再順帶提一下卡特蘭數的幾個應用。

什麼是卡特蘭數呢?卡特蘭數無非是一組有著某種規律的序列。重要的是它的應用。卡特蘭數前幾項為 : 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, ...

下面給出幾個求卡特蘭數的公式,用h(n)表示卡特蘭數的第n項,其中h(0)=1,h(1)=1

公式一:h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (n>=2)

公式二:h(n)=h(n-1)*(4*n-2)/(n+1)

公式三:h(n)=C(2n,n)/(n+1) (n=0,1,2,...)

公式四:h(n)=c(2n,n)-c(2n,n-1)(n=0,1,2,...)

下面程式碼用到的是公式一、公式二和公式四。

根據公式一求n<=35以內的卡特蘭數,由於卡特蘭數很大,超過35就超了long long 型了,所以n<=35時可以用公式一求解:

void init()
{
    int i,j;
    LL h[36];
    h[0]=h[1]=1;
    for(i=2;i<36;i++)
    {
  	h[i]=0;
	for(j=0;j<i;j++)
            h[i]=h[i]+h[j]*h[i-j-1];
    }
}

如果n>35時求h(n)%p應該怎麼求呢?由於h(n)是大數,這裡可以藉助Lucas(盧卡斯)定理來解決。

Lucas定理:Lucas定理是用來求 c(n,m) mod p,p為素數的值。Lucas定理的表示式為:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p) 有了這個公式,我們直接看Lucas定理的程式碼:

//Lucas定理實現C(n,m)%p的程式碼:
LL exp_mod(LL a, LL b, LL p) 
{ //快速冪取模
    LL res = 1;
    while(b != 0) 
    {
        if(b&1) res = (res * a) % p;
        a = (a*a) % p;
        b >>= 1;
    }
    return res;
}

LL Comb(LL a, LL b, LL p) 
{ //求組合數C(a,b)%p
    if(a < b)   return 0;
    if(a == b)  return 1;
    if(b > a - b)   b = a - b;

    LL ans = 1, ca = 1, cb = 1;
    for(LL i = 0; i < b; ++i) 
    {
        ca = (ca * (a - i))%p;
        cb = (cb * (b - i))%p;
    }
    ans = (ca*exp_mod(cb, p - 2, p)) % p;
    return ans;
}

LL Lucas(LL n,LL m,LL p) 
{ //Lucas定理求C(n,m)%p
     LL ans = 1;

     while(n&&m&&ans) 
    {
        ans = (ans*Comb(n%p, m%p, p)) % p;
        n /= p;
        m /= p;
     }
     return ans;
}

這樣根據公式四:h(n)=c(2n,n)-c(2n,n-1)(n=0,1,2,...) 就可以利用Lucas定理來求 :

h(n)%p=(Lucas(2n,n,p)-Lucas(2n,n-1,p)+p)%p。怎麼理解呢?對於兩個數a,b,(a-b)%p=(a%p-b%p)%p;那括號裡為什麼還要再加一個p呢?因為取模前前者一定大於後者,相減一定為正,而取模後就不一定了,所以要加一個p,保證是正數。

如果是要求卡特蘭大數呢?只要順便實現大數的一些運算就好了。下面直接給出程式碼:

int a[101][101],b[101];

void catalan() //求卡特蘭數
{
    int i, j, len, carry, temp;
    a[1][0] = b[1] = 1;
    len = 1;
    for(i = 2; i <= 100; i++)
    {
        for(j = 0; j < len; j++) //乘法
        a[i][j] = a[i-1][j]*(4*(i-1)+2);
        carry = 0;
        for(j = 0; j < len; j++) //處理相乘結果
        {
            temp = a[i][j] + carry;
            a[i][j] = temp % 10;
            carry = temp / 10;
        }
        while(carry) //進位處理
        {
            a[i][len++] = carry % 10;
            carry /= 10;
        }
        carry = 0;
        for(j = len-1; j >= 0; j--) //除法
        {
            temp = carry*10 + a[i][j];
            a[i][j] = temp/(i+1);
            carry = temp%(i+1);
        }
        while(!a[i][len-1]) //高位零處理
        len --;
        b[i] = len;
    }
}

以上可以處理n<=100時的卡特蘭大數,n再大可以把陣列相應開大。其中b[i]儲存的是第i位卡特蘭數的長度,a[i]陣列儲存的是第i位卡特蘭數的數值,高位存高位,低位存低位。

最後再簡單說一下卡特蘭數的應用,網上也給出了很多很好的解析,我只是把我覺得重要的整合了一下:

卡特蘭數的應用都可以歸結到一種情況:有兩種操作,分別為操作一和操作二,它們的操作次數相同,都為 N,且在進行第 K 次操作二前必須先進行至少 K 次操作一,問有多少中情況?結果就Catalan(N)。

Catalan數的典型應用:

1.由n個+1和n個-1組成的排列中,滿足字首和>=0的排列有Catalan(N)種。

2.括號化問題。左括號和右括號各有n個時,合法的括號表示式的個數有Catalan(N)種。

3.有n+1個數連乘,乘法順序有Catalan(N)種,相當於在式子上加括號。

4.n個數按照特定順序入棧,出棧順序隨意,可以形成的排列的種類有Catalan(N)種。

5.給定N個節點,能構成Catalan(N)種種形狀不同的二叉樹。

6.n個非葉節點的滿二叉樹的形態數為Catalan(N)。

7.對於一個n*n的正方形網格,每次只能向右或者向上移動一格,那麼從左下角到右上角的不同種類有Catalan(N)種。

8.對於在n位的2進制中,有m個0,其餘為1的catalan數為:C(n,m)-C(n,m-1)。

9.對凸n+2邊形進行不同的三角形分割(只連線頂點對形成n個三角形)數為Catalan(N)。

10.將有2n個元素的集合中的元素兩兩分為n個子集,若任意兩個子集都不交叉,那麼我們稱此劃分為一個不交叉劃分。此時不交叉的劃分數是Catalan(N)。

11.n層的階梯切割為n個矩形的切法數也是Catalan(N)。

12.在一個2*n的格子中填入1到2n這些數值使得每個格子內的數值都比其右邊和上邊的所有數值都小的情況數也是Catalan(N)。