题解:洛谷 P1040 加分二叉树

【题目来源】

洛谷:P1040 [NOIP 2003 提高组] 加分二叉树 - 洛谷

【题目描述】

设一个 \(n\) 个节点的二叉树 tree 的中序遍历为\((1,2,3,…,n)\),其中数字 \(1,2,3,…,n\) 为节点编号。每个节点都有一个分数(均为正整数),记第 \(i\) 个节点的分数为 \(d_i\),tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:

subtree 的左子树的加分 × subtree 的右子树的加分 + subtree 的根的分数。

若某个子树为空,规定其加分为 \(1\),叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为 \((1,2,3,…,n)\) 且加分最高的二叉树 tree。要求输出

  1. tree 的最高加分。
  2. tree 的前序遍历。

【输入】

\(1\)\(1\) 个整数 \(n\),为节点个数。

\(2\)\(n\) 个用空格隔开的整数,为每个节点的分数

【输出】

\(1\)\(1\) 个整数,为最高加分(\(Ans≤4,000,000,000\))。

\(2\)\(n\) 个用空格隔开的整数,为该树的前序遍历。

【输入样例】

5
5 7 1 2 10

【输出样例】

145
3 1 2 4 5

【核心思想】

  1. 问题分析:给定 \(n\) 个节点的中序遍历为 \(1, 2, \dots, n\),每个节点有权值 \(d_i\)。一棵二叉树的加分定义为:左子树加分 \(\times\) 右子树加分 \(+\) 根节点分数(空子树加分为 1)。要求构造一棵中序遍历不变的二叉树,使其加分最大,并输出最大加分和前序遍历。本质是区间 DP,因为中序遍历固定,所以任意子树对应一个连续区间 \([i, j]\),其根节点为区间内某个节点 \(k\),左子树为 \([i, k-1]\),右子树为 \([k+1, j]\)

  2. 算法选择

    • 区间 DP\(dp[i][j]\) 表示区间 \([i, j]\) 构成子树的最大加分。\(root[i][j]\) 记录使 \(dp[i][j]\) 最大的根节点编号,用于构造前序遍历。
    • 状态转移:对于区间 \([i, j]\),枚举根节点 \(k \in [i, j]\)
      • 左子树加分:\(dp[i][k-1]\)(若 \(i>k-1\),则为 1)
      • 右子树加分:\(dp[k+1][j]\)(若 \(k+1>j\),则为 1)
      • 总加分:\(dp[i][k-1] \times dp[k+1][j] + d_k\)
      • 取最大值,并记录对应的 \(k\)
    • 初始化:对于长度为 1 的区间,\(dp[i][i] = d_i\)(叶子节点加分即自身权值)。空子树加分视为 1,在转移时特殊处理。
  3. 关键步骤

    • 读入与初始化:读入 \(n\)\(d[1..n]\),初始化 \(dp[i][i] = d[i]\)
    • 区间 DP:按长度 \(len\) 从 2 到 \(n\) 枚举:
      • 枚举左端点 \(i\),计算右端点 \(j = i + len - 1\)
      • 初始化当前区间最大值为 \(dp[i][i] + dp[i+1][j]\)(根为 \(i\),左子树空视为 1,右子树为 \([i+1, j]\)),记录根为 \(i\)
      • 枚举根节点 \(k\)\(i+1\)\(j-1\)
        • 左子树加分:\(dp[i][k-1]\)(若 \(i>k-1\),则为 1)
        • 右子树加分:\(dp[k+1][j]\)(若 \(k+1>j\),则为 1)
        • 计算当前加分 \(= dp[i][k-1] \times dp[k+1][j] + d_k\)
        • 若当前加分更大,更新 \(dp[i][j]\)\(root[i][j]\)
      • 特殊处理根为 \(j\) 的情况(右子树空)。
    • 输出\(dp[1][n]\) 为最大加分;递归输出 \(root[1][n]\) 的前序遍历。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^3)\)\(n \le 30\),完全可行。
    • 空间复杂度:\(O(n^2)\),存储 DP 表和根节点记录。
  5. 区间 DP 的核心思想

    • 中序遍历固定:节点编号作为中序遍历顺序,使得任意子树对应一个连续区间,这是使用区间 DP 的前提。
    • 枚举根节点:区间 DP 的典型策略——对于一个区间,枚举可能作为根的节点,将问题划分为左右两个独立的子区间(子树),利用最优子结构进行合并。
    • 空子树处理:空子树加分为 1,这在转移时需特别注意,避免数组越界并保证乘法恒等元为 1。
    • 记录构造:通过 \(root[i][j]\) 记录最优根节点,便于后续前序遍历的重建。
    • 适用场景:适用于中序遍历固定、需要构造最优二叉树的场景,本质是卡特兰数相关的区间划分问题。

【算法标签】

普及+ #区间DP

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long  // 定义int为long long类型

const int N = 35;      // 定义最大节点数
int n;                  // 二叉搜索树的节点数量
int d[N];               // 存储每个节点的权值
int f[N][N];            // DP数组,f[i][j]表示区间[i,j]构成BST的最大分数
int root[N][N];         // 记录区间[i,j]的根节点位置

// 前序遍历输出BST结构
void dfs(int x, int y)
{
    // 当区间长度为1时直接输出
    if (x == y)
    {
        cout << x << ' ';
        return;
    }
    // 区间不合法时返回
    if (x > y) return;

    // 输出当前区间的根节点
    cout << root[x][y] << ' ';
    // 递归遍历左子树
    dfs(x, root[x][y] - 1);
    // 递归遍历右子树
    dfs(root[x][y] + 1, y);
}

signed main()  // 使用signed替代int(因为定义了int为long long)
{
    // 输入节点数量
    cin >> n;
    // 输入每个节点的权值并初始化DP数组
    for (int i = 1; i <= n; i++)
    {
        cin >> d[i];
        f[i][i] = d[i];  // 单个节点的分数就是其权值
    }

    // 动态规划处理所有区间
    for (int len = 2; len <= n; len++)  // 枚举区间长度
    {
        for (int i = 1; i + len - 1 <= n; i++)  // 枚举区间起点
        {
            int j = i + len - 1;  // 计算区间终点

            // 情况1:i作为根节点
            f[i][j] = f[i][i] + f[i + 1][j];
            root[i][j] = i;

            // 情况2:j作为根节点
            int t = f[i][j - 1] + f[j][j];
            if (t > f[i][j])
            {
                f[i][j] = t;
                root[i][j] = j;
            }

            // 情况3:k作为根节点(i < k < j)
            for (int k = i + 1; k < j; k++)
            {
                t = f[i][k - 1] * f[k + 1][j] + f[k][k];
                if (t > f[i][j])
                {
                    f[i][j] = t;
                    root[i][j] = k;
                }
            }
        }
    }

    // 输出最大分数
    cout << f[1][n] << endl;
    // 前序遍历输出BST结构
    dfs(1, n);

    return 0;
}

【运行结果】

5
5 7 1 2 10
145
3 1 2 4 5
posted @ 2026-08-30 09:17  团爸讲算法  阅读(11)  评论(0)    收藏  举报