• 卡特兰数


    欢迎来访

    首先看一下卡特兰数。若一个数列(h_n)满足:

    [h_n = sum_{i=0}^{n-1}h_i cdot h_{n-1-i} ]

    则称(h_n)为卡特兰数列。

    还有一种形式若:

    [h_n = frac{C_{2n}^n}{n+1} ]

    也称(h_n)为卡特兰数列

    那什么样的问题的答案是卡特兰数呢?

    满足条件的01序列

    题目链接

    分析

    (n)(3)时,满足条件的(01)序列为:

    000111
    001011
    001101
    010101
    010011

    我们可以将(01)序列转化到一个二维的平面当中,如果是(0)则向右走,反之则向上走,因此我们会走到((n,n))点,那么在平面中任意前缀序列中(0)的个数都不少于(1)代表的意思就是,对于途中的每一个点,横坐标要大于等于纵坐标,即路线不能越过(y=x)这条线。

    看一下以(n=6)的例子:


    图中橙色紫色满足条件。

    那满足条件的路线一共有多少呢?直接求不是很好求,所以我们可以转化一下,用所有的路线减去不满足条件的路线,那不满足条件的路线有什么样的特点呢?

    这样的路线上必然存在一点的纵坐标是大于横坐标的,即这样的路线一定经过了(y=x+1)这条线,以图中的褐色为例。我们将褐色的路线第一次经过(y=x+1)以后的路线关于(y=x+1)做轴对称,用黑色的线表示。我们可以发现所有不满足的条件的路线都可以通过这种方式将终点变为((n-1,n+1)),在这个例子中即为((5,7))。所有的路线为(C_{12}^6),因为一共有(12)个空,要放(6)(0)所以为(C_{12}^6),同理:不满条件的路线为(C_{12}^5)

    所以在一般问题中即为:

    [C_{2n}^n-C_{2n}^{n-1} ]

    在对其进行转化:

    (C_{2n}^n-C_{2n}^{n-1}= frac{(2n)!}{n!n!} - frac{(2n)!}{(n-1)!(n+1)!}= frac{(2n)!(n+1)-(2n)!n}{n!(n+1)!}= frac{(2n)!}{n!(n+1)!} = frac{(2n)!}{n!n!(n+1)} = frac{C_{2n}^n}{n+1})

    转化后即为:

    [frac{C_{2n}^n}{n+1} ]

    正好为开始讲的卡特兰数的第二种方式。

    C++代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    
    const int MOD = 1e9 + 7;
    
    int n;
    
    int qmi(int a, int b, int p) {
        int res = 1;
        while (b) {
            if (b & 1) res = (LL)res * a % MOD;
            b >>= 1;
            a = (LL)a * a % MOD;
        }    
        return res;
    }
    
    int main() {
    
        cin >> n;
        int a = 2 * n, b = n;
    
        int res = 1;
        // 求:2n * 2n-1 * ... * 2n-n+1
        for (int i = a; i >= a - b + 1; i--)
            res = (LL)res * i % MOD;
          
        // 除以 n! 之所以可以用费马小定理求逆元是因为,MOD为质数,且i不是MOD的倍数
        for (int i = b; i >= 1; i--)
            res = (LL)res * qmi(i, MOD - 2, MOD) % MOD;
        
        res = (LL)res * qmi(n + 1, MOD - 2, MOD) % MOD;
    
        cout << res << endl;
    
        return 0;
    }
    

    题目链接

    分析

    以样例为例,一共有五种方式:

    1push 2push 3push 3pop 2pop 1pop 序列为321
    1push 1pop 2push 2pop 3push 3pop 序列为123
    1push 2push 2pop 1pop 3push 3pop 序列为213
    1push 1pop 2push 3push 3pop 2pop 序列为132
    1push 2push 2pop 3push 3pop 1pop 序列为231

    我们发现是没有(312)这种序列的,因为(3)先出栈,就意味着,(3)曾经进栈,既然(3)都进栈了,那就意味着,(1)(2)已经进栈了,此时(2)一定在(1)上面,也就是更接近栈顶,所以(2)一定会先比(1)出栈,也就没有(312)这种序列。

    (push)(pop)的过程中必须满足:栈内有元素才能(pop),也就是说任意(push)(pop)序列的前缀中(push)的数量必须大于等于(pop)的数量,这样就与满足条件的(01)序列问题一样了。

    C++代码

    由于本题的数据范围很小所以求组合数时直接使用递推式:(C_n^m=C_{n-1}^m+C_{n-1}^{m-1})预处理出所有的组合数。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 40;
    
    int n;
    long long c[N][N];
    
    void init() {
        for (int i = 0; i < N; i++)
            for (int j = 0; j <= i; j++)
                if (!j) c[i][j] = 1;
                else c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
    }
    
    int main() {
        
        init();
        cin >> n;
        cout << c[2 * n][n] / (n + 1) << endl;
        
        return 0;
    }
    

    不同的二叉搜索树

    题目链接(AcWing)
    题目链接(LeetCode)

    记忆化搜索

    这个题刚做的时候是用dfs做的。但是在AcWing上只能过7个测试点,不过在LeetCode上还是能过的。

    AcWing代码
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    
    using namespace std;
    
    typedef long long LL;
    
    const int N = 1010, MOD = 1e9 + 7;
    
    int n;
    int f[N][N];
    
    LL dfs(int l ,int r) {
        if (l >= r) return 1;
        if (f[l][r] != -1) return f[l][r];
        f[l][r] = 0;
        for (int i = l; i <= r; i++) {
            f[l][r] = (f[l][r] + dfs(l, i - 1) * dfs(i + 1, r)) % MOD;
        }
        return f[l][r];
    }
    
    int main() {
        
        memset(f, -1, sizeof f);
        cin >> n;
        cout << dfs(1, n) << endl;
        
        return 0;
    }
    
    LeetCode代码

    C++

    class Solution {
    public:
        int numTrees(int n) {
            vector<vector<int>> a(n + 10, vector<int>(n + 10, -1));
            return dfs(1, n, a);
        }
        int dfs(int l, int r, vector<vector<int>> &a) {
            if (l >= r) return 1;
            if (a[l][r] != -1) return a[l][r];
            a[l][r] = 0;
            for (int i = l; i <= r; i++) {
                a[l][r] += dfs(l, i - 1, a) * dfs(i + 1, r, a);
            }
            return a[l][r];
        }
    };
    

    Java

    class Solution {
        public int numTrees(int n) {
            int[][] a = new int[n + 10][n + 10];
            for (int i = 0; i <= n; i++)
                for (int j = 0; j <= n; j++)
                    a[i][j] = -1;
            return dfs(1, n, a);
        }
        public int dfs(int l, int r, int[][] a) {
            if (l >= r) return 1;
            if (a[l][r] != -1) return a[l][r];
            a[l][r] = 0;
            for (int i = l; i <= r; i++) {
                a[l][r] += dfs(l, i - 1, a) * dfs(i + 1, r, a);
            }
            return a[l][r];
        }
    }
    
    正解 动态规划

    这个问题与有(n)个相同的节点构成的不同形态的二叉树是等价的。

    因为相同的节点构成的不同形态的二叉树的数量是固定的,而又因为本题是(BST)所以对于每一种形态上放整数(1)~(n)是唯一确定的。

    闫氏DP分析法

    • 状态表示:(f[n])表示有(n)个节点组成的不同(BTS)的数量
    • 状态计算:根据左儿子节点的数量划分,从(0) ~ (n-1),则右儿子节点的数量为(n-1) ~ (0)。对于每一种情况满足乘法原理,所以(f[n] = sum_{i=0}^{n-1}f[i] cdot f[n-1-i])

    到这里就会发现本题答案也是卡特兰数。

    C++代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long LL;
    
    const int N = 1010, MOD = 1e9 + 7;
    
    int n;
    int f[N];
    
    int main() {
        cin >> n;
        f[0] = 1; // 这个边界不能写成0
        for (int i = 1; i <= n; i++) 
            for (int j = 0; j < i; j++) 
                f[i] = (f[i] + (LL)f[j] * f[i - 1 - j]) % MOD;
            
        cout << f[n] << endl;
        
        return 0;
    }
    

    参考

    AcWing算法基础
    AcWing笔试面试
    大话数据结构

  • 相关阅读:
    使用secureCRT连接VMware-Ubuntukylin虚拟机
    java使用POI jar包读写xls文件
    SimpleDateFormat 相关用法
    ORACLE之表
    ORACLE之PACKAGE-游标变量
    PHP多进程学习(三)__代码案例来了解父进程与子进程的执行顺序
    PHP多进程学习(二)__fork起多个子进程,父进程的阻塞与非阻塞
    PHP多进程学习(二)__来初步了解一下PHP多进程及简单demo
    Python学习【三】
    Python学习【二】
  • 原文地址:https://www.cnblogs.com/optimjie/p/12740342.html
Copyright © 2020-2023  润新知