卡特蘭應用回顧(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

總結:卡特蘭數問題的突破口就是公式,要么通過遞推公式,要么通過組合公式.要會用計數問題中的一些常用技巧,比如取補集,轉換找特征等等.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM