[SMOJ1794]加分二叉树

97 篇文章 0 订阅
7 篇文章 0 订阅
根据给定的中序遍历,求解加分最高的二叉树,并输出其前序遍历。利用最优子结构,通过动态规划求解,确定每个子树的中序遍历范围并计算加分。
摘要由CSDN通过智能技术生成

题目描述

设一个 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 的前序遍历

输入格式 1794.in

第 1 行:一个整数 n n30 ),为节点个数。

第 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] 。由上面的推理得

f[l][r]=scoreX+max{f[l][X1]×f[X+1][r]} for each lXr

但是还要输出这棵二叉树的前序遍历呢?
参考之前“岛和桥”的思路,虽然之前是方案数而现在是输出树,但是道理是一样的。
在得到了中序遍历和根的情况下,也就得到了前序遍历,于是 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值