[蓝桥杯 2021 省 A] 左孩子右兄弟

GitHub跳转原题关系图返回列表

设 dp[u] 为以 u 为根能得到的最大二叉树高度,把最深孩子放到兄弟链最后,就有转移 dp[u]=儿子数+max(dp[child])。

OJ: luogu

题目 ID: P8744

难度:普及/提高-

标签:树形DP动态规划递推

日期: 2026-06-21 03:28

题意

给一棵多叉树,允许任意安排每个结点孩子的顺序,再按左孩子右兄弟表示法转成二叉树。

要求求出:

  • 所有可能转化结果中
  • 二叉树的最大高度

思路

先看一个可以直接验证想法的朴素解:

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 15;

int n;
vector<int> children[MAXN];

int brute_height(int u) {
    if (children[u].empty()) {
        return 0;
    }

    vector<int> perm = children[u];
    sort(perm.begin(), perm.end());

    int ans = 0;
    do {
        for (size_t i = 0; i < perm.size(); i++) {
            // 从 u 走到第 i 个孩子,需要先走 1 条左边,再走 i 条右兄弟边。
            ans = max(ans, 1 + (int)i + brute_height(perm[i]));
        }
    } while (next_permutation(perm.begin(), perm.end()));

    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // 这是一个小数据精确暴力:
    // 枚举每个结点儿子的排列顺序,直接按左孩子右兄弟定义求最高高度。
    cin >> n;
    for (int i = 1; i <= n; i++) {
        children[i].clear();
    }

    for (int i = 2; i <= n; i++) {
        int p;
        cin >> p;
        children[p].push_back(i);
    }

    cout << brute_height(1) << '\n';
    return 0;
}

brute.cpp 对每个结点枚举孩子排列,直接按定义求最大高度。 这个方法完全正确,但只能做很小的数据。

关键观察是:

如果结点 uk 个孩子,那么某个孩子若排在第 i 个位置,它最终对高度的贡献是:

1 + i + dp[child]

其中:

  • 1 来自左孩子边
  • i 来自它前面那些右兄弟边

所以如果我们想让高度尽量大,显然应该把“子树最高”的那个孩子放到最后。

于是立刻得到转移:

  • 叶子:dp[u] = 0
  • 非叶子:dp[u] = 儿子数 + max(dp[child])

因为题目保证父亲编号小于儿子编号,所以我们甚至不用 DFS 排序,直接从 n 倒着算到 1 就行。

样例树可以画成这样:

graph G {
  1 -- 2;
  1 -- 3;
  1 -- 4;
  2 -- 5;
}

这里根 1 有 3 个孩子。 为了让高度最大,应该把“还带着一个孩子 5” 的结点 2 放在兄弟链最后。 这样它能多吃到最多的右兄弟边,所以最终答案是 4

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100000 + 5;

int n;
vector<int> children[MAXN];
int parent_arr[MAXN];
int dp[MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        children[i].clear();
        parent_arr[i] = 0;
        dp[i] = 0;
    }

    for (int i = 2; i <= n; i++) {
        int p;
        cin >> p;
        parent_arr[i] = p;
        children[p].push_back(i);
    }

    // 由于保证父亲编号小于儿子编号,可以直接倒序做树形 DP。
    for (int u = n; u >= 1; u--) {
        int best_child = 0;
        for (size_t i = 0; i < children[u].size(); i++) {
            int v = children[u][i];
            best_child = max(best_child, dp[v]);
        }

        if (children[u].empty()) {
            dp[u] = 0;
        } else {
            // 把“收益最大”的儿子放到兄弟链的最后面,能多吃到最多的右兄弟边。
            dp[u] = (int)children[u].size() + best_child;
        }
    }

    cout << dp[1] << '\n';
    return 0;
}

复杂度

整棵树只需要线性扫描一次。

时间复杂度是 O(n)O(n),空间复杂度是 O(n)O(n)

总结

这题最核心的一句话就是:

  • 最深的那棵子树,应该放到最后一个兄弟位置

一旦看清这一点,整题就会化成一个非常短的树形 DP。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析