計蒜客 奇異家庭 (DP)


**鏈接 : ** Here!

思路 :

  1. 首先這棵家族樹非常非常非常有特點, 家族裏的人要麼沒有孩子, 要麼有兩個孩子, 所以這棵家族樹是一顆滿二叉樹.

  2. 設定狀態 $dp[i][j]$ 爲 $i$ 個人組成的不超過 $j$ 層的家譜結構種數, 首先明確一點, 那些狀態會爲這個狀態貢獻值 ? 自然能夠想到左右兩個孩子, 也就是 $dp[m][j-1]$, $dp[i-1-m][j-1]$ $(1 \leq m \leq i-2)$ , 那麼很自然的就能夠得到狀態轉移方程 :
    1. $dp[1][j] = 1, (1 \leq j \leq K)$
    2. $dp[i][j] = \prod_{m = 1}^{i - 2} {(dp[m][j - 1] * dp[i - 1 - m][j - 1])}, (m \neq 偶數, 2 \leq i \leq N, i \neq 偶數, 2 \leq j \leq K)$

  3. 爲什麼上面狀態轉移方程中 $i$ 和 $m$ 都必須爲奇數呢, 因爲家譜樹是一棵滿二叉樹, 所以節點數量只可能是奇數.

補充 :

  1. 滿二叉樹:樹中除了葉子節點,每個節點都有兩個子節點
  2. 完全二叉樹:在滿足滿二叉樹的性質後,最後一層的葉子節點均需在最左邊
  3. 完美二叉樹:滿足完全二叉樹性質,樹的葉子節點均在最後一層(也就是形成了一個完美的三角形)
  4. 一定要和國內的二叉樹分類相區別!!!

**代碼 : **

/*************************************************************************
	> File Name: 奇異家庭.cpp
	> Author: 
	> Mail: 
	> Created Time: 2017年11月21日 星期二 00時14分38秒
 ************************************************************************/

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

// 狀態轉移方程爲dp[i][j] : i個人數組成的不超過j層的家譜結構數
// 假設左子樹的節點數量爲m, 則右子樹的節點數量爲i - 1 - m, 注意左右子樹節點也一定得是奇數
// 因此dp[i][j] = SIGMA(dp[m][j - 1] * dp[i - 1 - m][j - 1]) (0 < m < i - 1)
const int MAX_N = 200 + 10;
const int MAX_K = 100 + 10;
const int MOD = 9901;
int dp[MAX_N][MAX_K];
int n, k;

void solve() {
    memset(dp, 0, sizeof(dp));
    for (int j = 1 ; j <= k ; ++j) {
        dp[1][j] = 1;
    }
    for (int j = 2 ; j <= k ; ++j) {
        // 注意奇數的情況下是不存在解的
        for (int i = 3 ; i <= n ; i += 2) {
            int sum = 0;
            for (int m = 1 ; m < i - 1 ; m += 2) {
                sum = (sum + (dp[m][j - 1] * dp[i - 1 - m][j - 1])) % MOD;
            }
            dp[i][j] = sum % MOD;
        }
    }
    // 注意, 如果不取模的話dp[n][k] >= dp[n][k - 1]
    // 但是如果有了取模運算,dp[n][k]就有可能小於dp[n][k - 1]了
    printf("%d\n", (dp[n][k] - dp[n][k - 1] + MOD) % MOD);
}
int main() {
    scanf("%d%d", &n, &k);
    if (!(n & 1))   printf("0\n");
    else            solve();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章