Catalan數 大數運算&&普通運算

卡特蘭數又稱卡塔蘭數,英文名Catalan number,是組合數學中一個常出現在各種計數問題中出現的數列。由以比利時的數學家歐仁•查理•卡塔蘭 (1814–1894)命名
原理
令h(0)=1,h(1)=1,catalan數滿足遞推式[1] :
h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)
例如:h(2)=h(0)*h(1)+h(1)*h(0)=1*1+1*1=2
h(3)=h(0)*h(2)+h(1)*h(1)+h(2)*h(0)=1*2+1*1+2*1=5
另類遞推式[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,…)
應用
應用大都可以轉化爲2n個包含01的序列要求任意前綴中0的個數都大於等於1的個數
1、括號化(左括號爲0右括號爲1)
矩陣連乘: P=a1×a2×a3×……×an,依據乘法結合律,不改變其順序,只用括號表示成對的乘積,試問有幾種括號化的方案?(h(n-1)種)
2、出棧次序
一個棧(無窮大)的進棧序列爲1,2,3,…,n,有多少個不同的出棧序列?[
類似:有2n個人排成一行進入劇場。入場費5元。其中只有n個人有一張5元鈔票,另外n人只有10元鈔票,劇院無其它鈔票,問有多少中方法使得只要有10元的人買票,售票處就有5元的鈔票找零?(將持5元者到達視作將5元入棧,持10元者到達視作使棧中某5元出棧)

類似:一位大城市的律師在他住所以北n個街區和以東n個街區處工作,每天她走2n個街區去上班。如果他從不穿越(但可以碰到)從家到辦公室的對角線,那麼有多少條可能的道路?
在<<計算機程序設計藝術>>,第三版,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)。
3、將多邊行劃分爲三角形問題
將一個凸多邊形區域分成三角形區域(劃分線不交叉)的方法數?

類似:在圓上選擇2n個點,將這些點成對連接起來使得所得到的n條線段不相交的方法數?
4、給定節點組成二叉樹問題。
給定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)個)

計算

long long h[40];//n<=35時的Catalan數
    h[0] = h[1] = 1;
    for (int i = 2; i < 40; i++)
    {
        h[i] = 0;
        for (int j = 0; j < i; j++)
        {
            h[i] += h[j]*h[i-1-j];
        }
    }
//根據應用2擴展不能穿越對角線通過DP可以推出h(n)
for (int i = 1; i <= n; i++) f[0][i] = 1;
for (int i = 1; i <= n; i++)
{
    for (int j = i; j <= n; j++)
        f[i][j] = f[i-1][j] + f[i][j-1];
}
#include<cstdio>//大數Catalan數
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
using namespace std;
const int maxn = 1005;
const int CNT = 1e4;
int cal[102][maxn];
int  len[maxn];
/*這兩個乘除算法可以通用,不過在使用時
要注意CNT不能太大否則很容易溢出*/
void Multy(int *ans, int b, int &len)
{                               
    int r = 0;//手動模擬下就知道步驟了
    for (int i = 0; i < len; i++)
    {
        int tmp = ans[i]*b + r;
        ans[i] = tmp % CNT;
        r = tmp / CNT;
    }
    while (r)
    {
        len++;
        ans[len-1] = r % CNT;
        r /= CNT;
    }
}
void Divid(int *ans, int b, int &len)
{
    int r = 0;
    for (int i = len-1; i >= 0; i--)
    {
        int tmp = ans[i]+r*CNT;
        ans[i] = (tmp/b) % CNT;
        r = tmp % b;
    }
    while (!ans[len-1])
    {
        len--;
    }
}


int main()
{
    //freopen("in", "r", stdin);
    int n;
    cal[0][0] = 1;
    len[0] = 1;
    for (int i = 1; i <= 100; i++)
    {   //根據遞推公式h(n)=h(n-1)*(4*n-2)/(n+1);
        memcpy(cal[i], cal[i-1], sizeof(cal[i]));
        len[i] = len[i-1];
        Multy(cal[i], 4*i-2, len[i]);
        Divid(cal[i], i+1, len[i]);
    }
    while(cin >> n && n)
    {
        printf("%d", cal[n][len[n]-1]);
        for (int i = len[n]-2; i >= 0; i--)printf("%04d", cal[n][i]);//注意格式
        cout << endl;
    }
    return 0;
}
發佈了42 篇原創文章 · 獲贊 11 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章