1. 问题描述:
设一个 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 行:一个整数 n,为节点个数。 第 2 行:n 个用空格隔开的整数,为每个节点的分数(0<分数<100)。
输出格式
第 1 行:一个整数,为最高加分(结果不会超过int范围)。第 2 行:n 个用空格隔开的整数,为该树的前序遍历。如果存在多种方案,则输出字典序最小的方案。
数据范围
n < 30
输入样例:
5
5 7 1 2 10
输出样例:
145
3 1 2 4 5
来源:https://www.acwing.com/problem/content/description/481/
2. 思路分析:
分析题目可以知道只给出一个中序遍历为(1,2,3,…,n)对应的二叉树的形态是不唯一的,我们需要在所有中序遍历为(1,2,3,...,n)的二叉树中找出加分最高的二叉树,因为字典序是最小的所以可以唯一确定这棵二叉树的形态,所以我们需要搜索所有可能的二叉树形态从而找到最最大得分的二叉树,由上面的分析可以知道当前的问题是一个最优化问题,可以考虑使用搜索(dfs)或者动态规划来解决,下面采用动态规划的思想解决。动态规划一般分为两个步骤来解决:① 状态表示;② 状态计算;我们可以声明一个二维数组dp,其中dp[i][j]表示所有中序遍历为[i,j]这一段二叉树的集合的最大加分;怎么样进行状态计算呢?状态计算对应集合的划分,一般找最后一个不同点,我们可以根据当前区间对应二叉树的根节点是区间中的哪一个数来划分,二叉树的根节点可以为区间[i,j]中的任何一个数,我们可以枚举当前区间中所有的根节点求解每一个集合的最大加分即可,因为二维dp数组中记录的是最大加分,题目中还需要输出最大加分对应的方案的前序遍历顺序,所以当前的二维数组dp不能够记录方案,与之前的解决方法一样我们可以声明一个额外的二维数组g来记录区间[i,j]最大加分对应的根节点,因为枚举区间的根节点的时候是从小到大枚举的所以更新的时候字典序一定是最小的,当子树加分更大的时候我们才更新当前区间的根节点,这样我们就利用数组g记录下每一个区间的最大加分的根节点了,然后我们使用递归输出根节点对应的前序遍历的顺序即可,每一次分为[l,k - 1]与[k + 1,r]来处理,结合g数组就可以输出区间最大得分的根节点了,递归的时候输出的方法与前序遍历是一模一样的。
3. 代码如下:
from typing import List
class Solution:
# 递归输出根节点对应的前序遍历
def dfs(self, l: int, r: int, g: List[List[int]]):
if l > r: return
# 根据当前的根节点那么就可以划分为两个以两个区间
k = g[l][r]
print(k, end=" ")
self.dfs(l, k - 1, g)
self.dfs(k + 1, r, g)
def process(self):
n = int(input())
# 列表前面加一个0这样存储的元素下标从1开始后面会比较好处理一点
a = [0] + list(map(int, input().split()))
dp = [[0] * (n + 1) for i in range(n + 1)]
# g用来记录当前区间能够获得最大加分的根节点
g = [[0] * (n + 1) for i in range(n + 1)]
for l in range(1, n + 1):
i = 1
while i + l - 1 <= n:
j = i + l - 1
# 区间长度为1那么当前的最大加分为当前位置的权重, 根节点为自己
if l == 1:
dp[i][j] = a[i]
g[i][j] = i
else:
# 根节点可以是左右区间的端点如果是端点那么加分为1
for k in range(i, j + 1):
left = 1 if k == i else dp[i][k - 1]
right = 1 if k == j else dp[k + 1][j]
score = left * right + a[k]
# 如果分数相等那么一开始的根节点就是字典序最小的所以不用更新只有当加分更大的时候才更新
if score > dp[i][j]:
dp[i][j] = score
g[i][j] = k
i += 1
print(dp[1][n])
self.dfs(1, n, g)
if __name__ == "__main__":
Solution().process()