当前位置: 首页 > news >正文

P1040 [NOIP 2003 提高组] 加分二叉树

题目描述

设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di​,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 个用空格隔开的整数,为该树的前序遍历。

输入输出样例

输入 #1

5
5 7 1 2 10

输出 #1

145
3 1 2 4 5

说明/提示

数据规模与约定

对于全部的测试点,保证 1≤n<30,节点的分数是小于 100 的正整数,答案不超过 4×109。

解析

一、问题分析

给定二叉树中序遍历得分规则:子树得分=左子树得分×右子树得分+根节点得分。要求:

  1. 计算最高加分
  2. 输出前序遍历序列

二、算法设计

1. 区间DP定义
  • dp[i][j]:中序遍历i~j子树的最高加分
  • root[i][j]:记录最优解的根节点位置
2. 状态转移方程

for(int len=1; len<=n; len++) {for(int i=1; i+len-1<=n; i++) {int j = i+len-1;for(int k=i; k<=j; k++) {int left = (k==i) ? 1 : dp[i][k-1];int right = (k==j) ? 1 : dp[k+1][j];int newScore = left * right + score[k];if(newScore > dp[i][j]) {dp[i][j] = newScore;root[i][j] = k;}}}
}

三、完整代码实现


#include <iostream>
#include <vector>
using namespace std;int n;
vector<int> score;
vector<vector<int>> dp, root;void printPreorder(int l, int r) {if(l > r) return;cout << root[l][r] << " ";printPreorder(l, root[l][r]-1);printPreorder(root[l][r]+1, r);
}int main() {cin >> n;score.resize(n+1);dp.assign(n+2, vector<int>(n+2, 0));root.assign(n+2, vector<int>(n+2, 0));for(int i=1; i<=n; i++) {cin >> score[i];dp[i][i] = score[i];root[i][i] = i;}for(int len=2; len<=n; len++) {for(int i=1; i+len-1<=n; i++) {int j = i+len-1;for(int k=i; k<=j; k++) {int left = (k==i) ? 1 : dp[i][k-1];int right = (k==j) ? 1 : dp[k+1][j];int newScore = left * right + score[k];if(newScore > dp[i][j]) {dp[i][j] = newScore;root[i][j] = k;}}}}cout << dp[1][n] << endl;printPreorder(1, n);return 0;
}

该代码实现了区间DP求解和树结构重建,时间复杂度O(n³)

四、代码批注

  1. 数据结构‌:

    • score存储节点分值
    • dp记录区间最优值
    • root记录根节点位置
  2. 初始化‌:

    • 单个节点的dp值为自身分数
    • 根节点为自身位置
  3. DP核心‌:

    • 三重循环分别处理区间长度、起点和分割点
    • 处理空子树情况(left/right=1)
  4. 输出处理‌:

    • 先输出最高加分
    • 递归输出前序遍历

五、复杂度分析

操作时间复杂度空间复杂度
输入处理O(n)O(n)
DP过程O(n³)O(n²)
前序遍历输出O(n)O(n)

六、算法证明

  1. 最优子结构‌:大区间最优解依赖小区间最优解
  2. 无后效性‌:长区间计算依赖已计算的短区间
  3. 正确性‌:遍历所有可能分割点确保全局最优

七、测试用例

输入:
5
5 7 1 2 10

输出:
145
3 1 2 4 5

解释:
最优二叉树结构:
3
/
1    4
\
2 5
得分计算:(5×7+1)×(2×1+10) = 36×4 + 3 = 145

八、总结与拓展

  1. 核心技巧‌:

    • 区间DP的经典应用
    • 树结构重建方法
  2. 同类问题‌:

    • 最优二叉搜索树
    • 矩阵链乘法
  3. 优化方向‌:

    • 四边形不等式优化(可降至O(n²))
    • 记忆化搜索实现
  4. 注意事项‌:

    • 空子树得分为1的特殊处理
    • 根节点数组的同步更新

该解法展示了如何将树形问题转化为区间DP模型,建议进一步练习:

  1. 洛谷P1273 有线电视网(树形DP)
  2. LeetCode 96.不同的二叉搜索树
  3. 尝试输出所有可能的最优解
http://www.dtcms.com/a/294563.html

相关文章:

  • 小米浏览器overflow不能左右滑动
  • spring-cloud概述
  • (Arxiv-2025)OVIS-U1技术报告
  • 想曰加密工具好用吗?本地安全、支持多算法的加密方案详解
  • NTC热敏电阻计算公式
  • 【大模型】Hugging Face常见模型格式详解
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-6,(知识点:二极管,少子多子,扩散/漂移运动)
  • mysql中ROW_NUMBER()、RANK()、DENSE_RANK()用法及区别
  • 在AI深度嵌入企业业务的当下——AI时代的融合数据库
  • 知己知彼:深入剖析跨站脚本(XSS)攻击与防御之道
  • React+Three.js实现3D场景压力/温度/密度分布可视化
  • 使用 piano_transcription_inference将钢琴录音转换为 MIDI
  • 2.4 PNIO-CM
  • 初级网安作业笔记3
  • opencv学习(视频读取)
  • Spring Data Redis 从入门到精通:原理与实战指南
  • 2025暑期—06神经网络-常见网络
  • JVM、Dalvik、ART区别
  • JS逆向实战案例之———x日头条【a-bogus】分析
  • 解析 Chromium 架构分层下 Windows 与 Linux 链接器行为差异及其影响
  • [深度学习] 大模型学习3下-模型训练与微调
  • 提升ARM Cortex-M系统性能的关键技术:TCM技术解析与实战指南
  • C++11扩展 --- 并发支持库(中)
  • sqlsuger 子表获取主表中的一个字段的写法
  • 第一章:Go语言基础入门之Go语言安装与环境配置
  • 顺丰面试提到的一个算法题
  • OpenAI发布ChatGPT Agent,AI智能体迎来关键变革
  • Git原理及使用
  • android studio打包vue
  • Android Studio中调用USB摄像头