Noip2003加分二叉树题解

  • 题目描述 Description
    设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
    subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
    若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空
    子树。
    试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出:
    (1)tree的最高加分
    (2)tree的前序遍历
    现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。

  • 输入描述 Input Description
    第1行:一个整数n(n<=30),为节点个数。
    第2行:n个用空格隔开的整数,为每个节点的分数(分数<=100)

  • 输出描述 Output Description
    第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。
    第2行:n个用空格隔开的整数,为该树的前序遍历。

  • 样例输入 Sample Input
    5
    5 7 1 2 10

  • 样例输出 Sample Output
    145
    3 1 2 4 5

  • 数据范围及提示 Data Size & Hint
    n(n<=30)
    分数<=100

  • 题解
    一看n的范围就想到是搜索题,但是,本题中n如此小的范围只是不想让我们写高精而已。其实二叉树的中序遍历相当于一个区间,这样的性质在Splay里体现得较多。用a[]来储存每个结点的分数,那么考虑一段中序遍历,即一段区间[l,r],其最大加分可计算如下: f(l,r)=max(f(l,k1)f(k+1,r)+a[k]) ,其中 f(i,i)=a[i],(i=0..n) k 表示区间[l,r]的父亲结点。
    这其实是区间dp的思想。至于输出其前序遍历,只需在状态转移时记录最优决策(即该段区间的父亲)即可。在中序遍历中树根是把把左右子树分开的,输出时递归输出即可。
    为了不进行大量的边界判断,.可以把f数组全部初始化为1。

  • Code:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 50;
typedef unsigned long long ll;
ll f[maxn][maxn], a[maxn], n, pre[maxn][maxn];
inline void read(ll &a) {
    int f = 1;
    char c = getchar();
    a = 0;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') {
        a = a*10 + c - 48;
        c = getchar();
    }
    a *= f;
}
inline void write(ll a) {
    int t = 0;
    char c[30];
    if(a < 0) { putchar('-'); a = -a; }
    do {
        c[t++] = a%10 + 48;
        a /= 10;
    } while(a);
    while(t--) putchar(c[t]);
    putchar(' ');
}
void print(int l, int r) {
    if(l >= r) { write(l); return; }
    int rot = pre[l][r];
    write(rot);
    if(l < rot) print(l, rot - 1);
    if(r > rot) print(rot + 1, r);
}
inline void init() {
    read(n);
    for(int i = 1; i <= n; ++i) read(a[i]);
}
inline void work() {
    for(int i = 0; i <= n; ++i) for(int j = 0; j <= n; ++j) f[i][j] = 1;
    for(int i = 1; i <= n; ++i) f[i][i] = a[i];
    for(int p = 2; p <= n; ++p) for(int i = 1; i <= n - p + 1; ++i) {
        int j = i + p - 1;
        for(int k = i; k <= j; ++k)
            if(f[i][j] < f[i][k-1]*f[k+1][j] + a[k]) {
                f[i][j] = f[i][k-1]*f[k+1][j] + a[k];
                pre[i][j] = k;
            }
    }
    write(f[1][n]); putchar('\n');
    print(1, n);
}
int main() {
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值