计蒜客 奇异家庭 (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;
}
posted @ 2017-11-23 15:24  ojnQ  阅读(231)  评论(0编辑  收藏  举报