NC16681 [NOIP2003]加分二叉树

题目链接

题目

题目描述

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

输入描述

第1行:一个整数n(n<30),为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。

输出描述

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

示例1

输入

5
5 7 1 2 10

输出

145
3 1 2 4 5

题解

知识点:DFS,树,区间dp。

因为给出的是中序遍历,因此一个区间可以作为一个树,两个区间加一个中间的点可以作为做左子树根节点和右子树,于是可以区间dp。

\(dp[i][j]\) 为区间 \([i,j]\) 构成树后的最高分,有转移方程:

\[dp[i][j] = \max (1,dp[i][k-1])\cdot \max (1,dp[k+1][j]) + a[k],k \in [i,j] \]

\(k = i \text{ or } j\) 时,表示的是空树,应该用 \(1\) 代替。

可以边dp边记录某个树 \([i,j]\) 的父节点 \(fa[i][j]\) ,因为 \(k\) 本身是作为根节点划分区间。

最后dfs一下 \(fa[1][n]\) 即可。

时间复杂度 \(O(n^3)\)

空间复杂度 \(O(n^2)\)

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int a[37], fa[37][37];
ll dp[37][37];

void dfs(int l, int r) {
    if (l > r) return;
    cout << fa[l][r] << ' ';
    dfs(l, fa[l][r] - 1);
    dfs(fa[l][r] + 1, r);
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> a[i], dp[i][i] = a[i], fa[i][i] = i;
    for (int l = 2;l <= n;l++) {
        for (int i = 1, j = l;j <= n;i++, j++) {
            for (int k = i;k <= j;k++) {
                ll t = max(1LL, dp[i][k - 1]) * max(1LL, dp[k + 1][j]) + a[k];
                if (dp[i][j] < t) {
                    dp[i][j] = t;
                    fa[i][j] = k;
                }
            }
        }
    }
    cout << dp[1][n] << '\n';
    dfs(1, n);
    return 0;
}
posted @ 2022-08-15 21:06  空白菌  阅读(63)  评论(0编辑  收藏  举报