• 『笔记』数学数论(七)


    \[\Huge{(七)组合数学之 \color{#3086B1}{卡特兰数}} \]

    简介

    卡特兰数(Catalan),又称明安图数,是组合数学中一个常出现于各种计数问题的数列。

    对应序列为:

    \(1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, \cdots\)

    满足

    \[M_{n+1} = M_0M_n + M_1M_{n-1} + \cdots + M_nM_0 \]

    其中 \(C_0=1,C_1=1\)

    性质

    注:为避免符号重复本文所有公式将以 \(M_n\) 表示卡特兰数,即明安图数的拼音首字母。

    符合通项公式:

    \[M_{n}=C_{2 n}^{n}-C_{2 n}^{n+1}=\frac{C_{2 n}^{n}}{n+1}\left(n \geq 2, n \in N _{+}\right) \]

    那么可以推出如下公式:

    \[\begin{aligned} M_{n+1} &=\frac{M_{n}(4 n-2)}{n+2} & \\ M_{n} &=\left\{ \begin{array}{ll} \sum_{i=1}^{n} M_{i-1} M_{n-i} & n \geq 2, n \in N _{+} \\ 1 & n=0,1 \end{array}\right.\\ M_{n} &= \begin{bmatrix} 2 n \\ n \end{bmatrix}- \begin{bmatrix} 2 n \\ n-1 \end{bmatrix} \end{aligned} \]

    基本原理

    先来看一个问题:

    对于 \(n\) 对括号,求共有多少种合法的匹配方案数。

    其中括号的合法匹配方式为:一个左括号对应一个右括号,且左括号必须要在右括号前面出现。

    这里我们用 +1 表示 “ \((\) ” ,用 -1 表示 “ \()\) ”。

    那么显然,对于一组合法的括号序列,该序列的前缀和必然大于或等于 \(0\) ;相反,若该括号序列不合法,其前缀和必然存在小于 \(0\) 的情况。

    首先给出一个序列:

    ()(())()
    

    该序列可以表示为

    +1 -1 +1 +1 -1 -1 +1 -1
    

    显然这是一个非法序列。

    对于像这样的一个非法序列,找到第一个前缀和小于 \(0\) 的括号,并对该前缀中的每一个数进行取反。

    上述例子便可以得到

    -1 +1 +1 +1 -1 +1 +1 -1
    

    此时该序列中共有 \(3+1+1=5\)+1\(4-1=3\)-1 。不难看出,第一个小于 \(0\) 的前缀和必定为 \(-1\) ,即 \(-1\)\(+1\) 多一个,取反后则 \(-1\)\(+1\) 少一个。这样总体上看,由于二者数量原本相同,所以 \(+1\) 必定变为 \(n+1\) 个,\(-1\) 则变为 \(n-1\) 个。

    由此,该结论可以推广:

    对于 \(n\) 对括号的每种非法匹配序列 \(A\),一定会有一个含有 \(n+1\)\(+1\)\(n-1\)\(-1\) 的序列 \(B\) 与其一一对应。

    而序列 \(B\) 的数量可以通过 \(C_{2n}^{n+1}\) 计算,即非法序列的数量为 \(C_{2n}^{n+1}\)

    而序列的总数量为 \(C_{2n}^n\) (从 \(2n\) 个位置中选择 \(n\) 个位置放左括号,不考虑先后顺序),则合法的匹配序列数量为:

    \[C_{2 n}^{n}-C_{2 n}^{n+1}=\cfrac{C_{2 n}^{n}}{n+1} \]

    由此便推导出了卡特兰数的通项公式。

    应用

    P1044 [NOIP2003 普及组] 栈

    直接由基本原理可以推出:

    公式 1

    \[H_n=\cfrac{H_{n−1} \times 4 \times n−2}{n+1} \]

    代码:

    /*
    
    Name: P1044 [NOIP2003 普及组] 栈
    
    Solution: 
    
    
    By Frather_
    
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    
    #define int long long
    
    using namespace std;
    /*==================================================快读*/
    inline int read()
    {
        int X = 0, F = 1;
        char CH = getchar();
        while (CH < '0' || CH > '9')
        {
            if (CH == '-')
                F = -1;
            CH = getchar();
        }
        while (CH >= '0' && CH <= '9')
        {
            X = (X << 3) + (X << 1) + (CH ^ 48);
            CH = getchar();
        }
        return X * F;
    }
    /*===============================================定义变量*/
    const int _ = 110;
    
    int n;
    int h[_];
    /*=============================================自定义函数*/
    
    /*=================================================主函数*/
    signed main()
    {
        h[0] = 1;
        h[1] = 1;
        n = read();
        for (int i = 2; i <= n; i++)
            h[i] += h[i - 1] * (4 * i - 2) / (i + 1);
        printf("%lld\n", h[n]);
        return 0;
    }
    

    公式 2

    \[H_n=H_0 \times H_{n-1}+H_1 \times H_{n-2} + \cdots + H_{n-1} \times H_0 (n \geq 2) \]

    代码:

    /*
    
    Name: P1044 [NOIP2003 普及组] 栈
    
    Solution: 
    
    
    By Frather_
    
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    
    #define int long long
    
    using namespace std;
    /*==================================================快读*/
    inline int read()
    {
        int X = 0, F = 1;
        char CH = getchar();
        while (CH < '0' || CH > '9')
        {
            if (CH == '-')
                F = -1;
            CH = getchar();
        }
        while (CH >= '0' && CH <= '9')
        {
            X = (X << 3) + (X << 1) + (CH ^ 48);
            CH = getchar();
        }
        return X * F;
    }
    /*===============================================定义变量*/
    const int _ = 110;
    
    int n;
    int h[_];
    /*=============================================自定义函数*/
    
    /*=================================================主函数*/
    signed main()
    {
        h[0] = 1;
        h[1] = 1;
        n = read();
        for (int i = 2; i <= n; i++)
            for (int j = 0; j < i; j++)
                h[i] += h[j] * h[i - j - 1];
        printf("%lld\n", h[n]);
        return 0;
    }
    

    公式 3

    \[H_n =\cfrac{C_{2n}^n}{n+1}(n=0,1,2,\cdots) \]

    其中

    \[C_m^n = C_{m-1}^{n-1} + C_{m-1}^n \]

    并且规定

    \[C_n^0=1, C_n^n=1, C_0^0=1 \]

    代码:

    /*
    
    Name: P1044 [NOIP2003 普及组] 栈
    
    Solution: 
    
    
    By Frather_
    
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    
    #define int long long
    
    using namespace std;
    /*==================================================快读*/
    inline int read()
    {
        int X = 0, F = 1;
        char CH = getchar();
        while (CH < '0' || CH > '9')
        {
            if (CH == '-')
                F = -1;
            CH = getchar();
        }
        while (CH >= '0' && CH <= '9')
        {
            X = (X << 3) + (X << 1) + (CH ^ 48);
            CH = getchar();
        }
        return X * F;
    }
    /*===============================================定义变量*/
    const int _ = 110;
    
    int n;
    int h[_ << 1][_];
    /*=============================================自定义函数*/
    
    /*=================================================主函数*/
    signed main()
    {
        n = read();
        for (int i = 1; i <= n << 1; i++)
        {
            h[i][0] = 1;
            h[i][i] = 1;
            for (int j = 1; j < i; j++)
            {
                h[i][j] = h[i - 1][j] + h[i - 1][j - 1];
            }
        }
        printf("%lld\n", h[n << 1][n] / (n + 1));
        return 0;
    }
    

    公式 4

    \[H_n=C_{2n}^n−C_{2n}^{n−1}(n=0,1,2,\cdots) \]

    代码:

    /*
    
    Name: P1044 [NOIP2003 普及组] 栈
    
    Solution: 
    
    
    By Frather_
    
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    
    #define int long long
    
    using namespace std;
    /*==================================================快读*/
    inline int read()
    {
        int X = 0, F = 1;
        char CH = getchar();
        while (CH < '0' || CH > '9')
        {
            if (CH == '-')
                F = -1;
            CH = getchar();
        }
        while (CH >= '0' && CH <= '9')
        {
            X = (X << 3) + (X << 1) + (CH ^ 48);
            CH = getchar();
        }
        return X * F;
    }
    /*===============================================定义变量*/
    const int _ = 110;
    
    int n;
    int h[_ << 1][_];
    /*=============================================自定义函数*/
    
    /*=================================================主函数*/
    signed main()
    {
        n = read();
        for (int i = 1; i <= n << 1; i++)
        {
            h[i][0] = 1;
            h[i][i] = 1;
            for (int j = 1; j < i; j++)
                h[i][j] = h[i - 1][j] + h[i - 1][j - 1];
        }
        printf("%lld\n", h[n << 1][n] - h[n << 1][n - 1]);
        return 0;
    }
    

    如果还有其他公式欢迎联系博主!(博主最多推四个了。。)

  • 相关阅读:
    使用C++为对象分配与释放内存时的几个好习惯
    OGRE渲染流程
    【问题解决记录】无法识别的标志“-sdlMode”,在“p2”中
    四元数 Quaternion
    《The Cg Tutorial》阅读笔记——凹凸贴图 Bump Mapping
    尝试优化骨骼动画计算的意外收获——使用嵌入式汇编对float转int进行优化
    Model 的 Meta 选项
    dns资料
    ansible中的变量
    DockerFile与docker-compose.yml是什么
  • 原文地址:https://www.cnblogs.com/Frather/p/14702324.html
Copyright © 2020-2023  润新知