卡特兰应用回顾(01序列,二叉搜索树个数与方案)

卡特兰数

定义

卡特兰数又称卡塔兰数,卡特兰数是组合数学中一个常出现在各种计数问题中的数列,关于卡特兰数的题目大多都有一个差不多的套路:对于一个规模为n的问题,先找一个元素固定下来,然后将剩下的n-1个元素拆分成两个子问题

若一个数列\(f_n\)满足:

\[f(n) = f(0)*f(n-1) + f(1)*f(n-2)+......+f(n-1)*f(0)\\ =\sum_{i=0}^{n-1} f_{i} \cdot f_{n-1-i} \]

则称\(f_n\)为卡特兰数列。

还有一种形式若:

\[f_{n}=C(2n,n) - C(2n,n-1)\\ =\frac{C_{2 n}^{n}}{n+1} \]

也称\(f_n\)为卡特兰数列

常见公式

\[f(n) = f(0)*f(n-1) + f(1)*f(n-2)+......+f(n-1)*f(0)\\ f_{n}=\frac{C_{2 n}^{n}}{n+1}\\ f(n) = f(n-1)*(4*n-2) / (n+1).\\ f(n) = C(2n,n) - C(2n,n-1)\\ \]

例题

满足条件的01序列

题意

AcWing 889. 满足条件的01序列

题目描述: 给定 \(n\) 个 0 和 \(n\) 个 1 ,它们将按昭某种顺序 排成长度为 \(2 n\) 的序列,求它们能排列成的所有序列中, 能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序 列有多少个。答案对 \(10^{9}+7\) 取模。
数据范围: \(1 \leq n \leq 10^{5}\)

分析

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

000111
001011
001101
010101
010011

\(01\) 序列置于坐标系中,起点定于原点。若 \(0\) 表示向右走,\(1\) 表示向上走,那么任何前缀中 \(0\) 的个数不少于 \(1\) 的个数就转化为,路径上的任意一点,横坐标大于等于纵坐标。题目所求即为这样的合法路径数量。

下图中,表示从 \((0,0)\) 走到 \((n,n)\) 的路径,在绿线及以下表示合法,若触碰红线即不合法。

Catalan.png

由图可知,任何一条不合法的路径(如黑色路径),都对应一条从 \((0,0)\) 走到 \((n-1,n+1)\) 的一条路径(如灰色路径)。而任何一条 \((0,0)\) 走到 \((n-1,n+1)\) 的路径,也对应了一条从 \((0,0)\) 走到 \((n,n)\) 的不合法路径。

答案如图,即卡特兰数。

\[C_{2 n}^{n}-C_{2 n}^{n-1}=\frac{(2 n) !}{n ! n !}-\frac{(2 n) !}{(n-1) !(n+1) !}\\ =\frac{(2 n) !(n+1)-(2 n) ! n}{n !(n+1) !}=\frac{(2 n) !}{n !(n+1) !}\\ =\frac{(2 n) !}{n ! n !(n+1)}\\ =\frac{C_{2 n}^{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}\)预处理出所有的组合数。时间复杂度是 \(O\left(n^{2}\right)\)

#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;
}

不同的二叉搜索树

leetcode 96. 不同的二叉搜索树

leetcode 95. 不同的二叉搜索树 II

题意

给你一个整数 \(n\) ,求恰由 \(n\) 个节点组成且节点值从 1 到 \(n\) 互不相同的二叉搜索树有多少种? 返回满足题意的二叉搜索树的种数。

并输出其方案数目.

img

算法

先求如何输出方案:

  1. 对于每段连续的序列 \(l, l+1, \ldots r\) ,枚举区间中的数\(i\)作为当前二叉搜索树的根结点,并在\([l,i - 1]\)中构造出属于左子树的二叉搜索树的集合,在\([i + 1,r]\)中构造出属于右子树的二叉搜索树的集合;
  2. 分别递归求出左右子树的所有方案;
  3. 乘法原理:左子树的任意一种方案和右子树的任意一种方案拼在一起,可以得到当前节点的一种方案,所以我们将左右子树的所有方案 两两组合,并记录在答案中。

进一步想如何求出方案数目:

状态表示: \(f[n]\) 表示 \(\mathrm{n}\) 个节点的二叉搜索树共有多少种。

状态转移: 左子树可以有 \(0,1, \ldots n-1\) 个节点,对应的 右子树有 \(n-1, n-2, \ldots, 0\) 个节点, \(f[n]\) 是所有这些情况的加和,所以 \(f[n]=\sum_{k=0}^{n-1} f[k] * f[n-1-k]\)
时间复杂度分析:状态总共有 \(n\) 个,状态转移的复杂度是 \(O(n)\) ,所以总时间复杂度是 \(O\left(n^{2}\right)\)

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

C++代码

计数

class Solution {
public:
    int numTrees(int n) {
        vector<int>f(n + 1);
        f[0] = 1;
        for (int i = 1; i <= n; i ++ )
        {
            for (int j = 1; j <= i; j ++ )
                f[i] += f[j - 1] * f[i - j];
        }
        return f[n];
    }
};

输出方案

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<TreeNode*> dfs(int l, int r){
        if(l > r) return {NULL};
        vector<TreeNode*> res;
        for(int i = l; i <= r; i++){
            auto L = dfs(l, i - 1), R = dfs(i + 1, r);
            for(auto lc: L){
                for(auto rc: R){
                    TreeNode* root = new TreeNode(i);
                    root->left = lc;
                    root->right = rc;
                    res.push_back(root);
                }
            }
        }
        return res;
    }
    vector<TreeNode*> generateTrees(int n) {
        return dfs(1, n);
    }
};

应用

例1:二叉树的计数

已知一颗二叉树有n个节点,问:该二叉树能组成多少种不同的形态?

分析:常规套路,先选一个点当根节点,用f(n)表示n个节点的二叉树不同的形态数,假设左子树有i个节点,右子树有n-i-1个节点,那么根据乘法原理,就有f(i)*f(n-i-1)种方案.把每个点为根的方案数加起来就是答案:f(n) = Σf(i)*f(n-i-1).

例2:AB排列问题

有n个A和n个B排成一排,从第1个位置开始到任何位置,B的个数不能超过A的个数,问:这样的排列有多少种?

分析:从公式入手.符合要求的排列数=总的排列数-不符合要求的排列数.因为要在2n个位置中选择n个位置放A,所以总的排列数为C(2n,n).再来看如何求不符合要求的排列数.首先肯定是要找不符合要求的排列有什么特征,一个不符合要求的排列的特征是:存在一个奇数位2*k+1,有k+1个B,k个A,根据这个还不能得到答案.将2*k+2到n*2位上的A,B互换一下,可以发现最后得到的排列一定是有n+1个B,n-1个A的,也就是说一旦一个排列经过这种操作后有n+1个B,那么这个排列就是不合法的,因为变换前的排列和变换后的排列是一一对应的,我们只需要求出变换后的排列有多少种,就能知道变换前的排列有多少种.即C(2n,n+1)种,答案为C(2n,n)-C(2n,n+1).正好就是卡特兰数.

例3:乘法加括号:对于连乘a1*a2*a3*......*an,加了括号后就可改变它的运算顺序。问:有多少种不同的运算顺序.

分析:常规套路,先选一个乘号当根节点,构造二叉树,乘号左边就是它的左子树,乘号右边就是它的右子树,剩下的就转化为了例1.

例5:欧拉多边形分割问题:设有一个凸多边形,可以用n-3条不相交的对角线将n边形分成n-2个互相没有重叠的三角形,例如n=5,有5种方法.

分析:还是要先找一个元素给固定下来,对于边V1Vn,任选一顶点Vk,向V1和Vn连边。将三角形V1VnVk分割出去,剩下两个多边形,一个多边形有顶点{1,2,3,…,k},所以是k边形;另一个多边形有顶点{k,k+1,…,n},所以是(n-k+1)边形。这就是类卡特兰数了.最后的答案为f(n) = Σf(k)*f(n-k+1).

具体解释:https://blog.csdn.net/qq_37685156/article/details/79714609

总结:卡特兰数问题的突破口就是公式,要么通过递推公式,要么通过组合公式.要会用计数问题中的一些常用技巧,比如取补集,转换找特征等等.

posted @ 2022-01-10 11:50  pxlsdz  阅读(3751)  评论(0编辑  收藏  举报