您的位置:首页 > 其它

Catalan数 大数运算&&普通运算

2015-08-20 11:10 232 查看
卡特兰数又称卡塔兰数,英文名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)个)

计算

[code]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];
        }
    }


[code]//根据应用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];
}


[code]#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
[len
-1]);
        for (int i = len
-2; i >= 0; i--)printf("%04d", cal
[i]);//注意格式
        cout << endl;
    }
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: