题目描述
设一个 n 个节点的二叉树 tree 的中序遍历为 (
1,2,3,…,n ),其中数字 1,2,3,…,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为di , tree 及它的每个子树都有一个加分,任一棵子树 subtree (也包含 tree 本身)的加分计算方法如下:subtree 的左子树的加分 × subtree 的右子树的加分+ subtree 的根的分数
若某个子树为空,规定其加分为 1 ,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为 ( 1,2,3,…,n ) 且加分最高的二叉树 tree 。要求输出:
- tree 的最高加分
- tree 的前序遍历
输入格式 1794.in
第 1 行:一个整数 n (
n<30 ),为节点个数。第 2 行: n 个用空格隔开的整数,为每个节点的分数(分数< 100 )。
输出格式 1794.out
第 1 行:一个整数,为最高加分(结果不会超过 4,000,000,000 )。
第 2 行:n 个用空格隔开的整数,为该树的前序遍历。
输入样例 1794.in
5
5 7 1 2 10
输出样例 1794.out
145
3 1 2 4 5
这题的题目意思正如其题面所述,应该比较容易理解。值得一提的是此题为 NOIp 2003 原题。
那么,我们的任务就是:在确定了一棵二叉树的中序遍历情况下,要构造一棵加分最大的二叉树。中序遍历是要利用好的关键条件,如何利用呢?
我们回到定义,从二叉树的中序遍历到底怎么来的入手。
所谓二叉树的中序遍历,由其左儿子的中序遍历、根结点、右儿子的中序遍历依次连接组成,空树的中序遍历为空。
也就意味着,在确定了中序遍历和根结点的情况下,也就确定了左儿子的中序遍历和右儿子的中序遍历。
那我们如何得到加分最大的二叉树的根结点呢?
枚举一下嘛。
当在中序遍历中枚举确定了根结点之后,有唯一的左子树中序遍历和右子树中序遍历与之对应,然后又可以分别构造左子树和右子树。
用 DP 的术语来说,这题满足了“最优子结构性质”。(如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质)
这意味着我们就可以在确定根结点之后,先分别构造最优的左子树和右子树,这样最终得到的也是最优的二叉树。
又回到如何描述状态的问题。
既然一棵最优的加分二叉树与一个中序遍历一一对应,而整个树的中序遍历是确定的,那么只要确定了子二叉树的中序遍历在原二叉树的中序遍历的位置,就可以得到最优的加分子二叉树了。
于是不妨设
f[l][r]
表示中序遍历在整棵树的中序遍历中位置为
[l,r]
的子树的最高加分,所求即为
f[1][n]
。由上面的推理得
但是还要输出这棵二叉树的前序遍历呢?
参考之前“岛和桥”的思路,虽然之前是方案数而现在是输出树,但是道理是一样的。
在得到了中序遍历和根的情况下,也就得到了前序遍历,于是 DP 的时候只要更新了,就顺带记一下相应区间的最高加分二叉树的根结点即可。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 50;
int n;
int score[maxn];
int root[maxn][maxn]; //root[l][r] 保存中序遍历为 [l, r] 的加分最大二叉树的根
int dp[maxn][maxn];
int solve(int l, int r) { //采用记忆化搜索实现 也可直接 dp
if (dp[l][r] != -1) return dp[l][r];
if (l > r) return 1; //子树空
if (l == r) { //边界
root[l][r] = l;
return score[l];
}
int ans = 0;
for (int i = l; i <= r; i++) {
int s = solve(l, i - 1) * solve(i + 1, r) + score[i];
if (s > ans) { ans = s; root[l][r] = i; }
}
return dp[l][r] = ans;
}
void preOrder(int l, int r) { //输出树的前序遍历
if (l > r) return ;
printf("%d ", root[l][r]);
preOrder(l, root[l][r] - 1);
preOrder(root[l][r] + 1, r);
}
int main(void) {
freopen("1794.in", "r", stdin);
freopen("1794.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &score[i]);
memset(dp, -1, sizeof dp);
printf("%d\n", solve(1, n));
preOrder(1, n);
return 0;
}