题意
有一颗二叉树,树的每一个节点都有一个值,设他的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。任一棵子树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个用空格隔开的整数,为该树的前序遍历。
解析
首先我们知道只靠中序遍历并不能确定一个二叉树,通过中序遍历我们可以知道根的左边是左子树,右边是右子树,并且在这个题目中中序遍历是连续的,也就是说我们可以得出这个左子树所在的区间,根,右子树所在的区间连在一起是一个连续区间,那么我们要求最高得分,首先来看这个记分规则,左子树的分数乘以右子树的分数加上根的分数,这么来看,首先我们要让左子树右子树的分数尽可能的个高,这么看我们把左子树右子树分开这个正好是区间dp哒,对于dp而言最重要的就是dp数组的含义哒,这里我们用二维数组dp[i][j]表示中序遍历[i,i+1,...j-1,j],的最大值,那么状态转移就是根节点值加上左子树最大乘右子树最大,在区间[i,j]中枚举全部的决策点k,计算到最大就是我们要的值,还有一个点就是要特判一下没有左右子树的情况。代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; inline ll read() { ll s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } const int N = 35; ll dp[N][N], rt[N][N]; int a[N]; void dfs(int l, int r) { if (l > r) return; int k = rt[l][r]; printf("%d ", k); dfs(l, k - 1); dfs(k + 1, r); } int main() { int n = read(); for (int i = 1; i <= n; ++i) a[i] = read(); for (int len = 1; len <= n; ++len) { for (int l = 1; l + len - 1 <= n; ++l) { int r = l + len - 1; for (int k = l; k <= r; ++k) { ll lson = k == l ? 1 : dp[l][k - 1]; ll rson = k == r ? 1 : dp[k + 1][r]; ll tmp = lson * rson + a[k]; if (l == r) tmp = a[k]; if (tmp > dp[l][r]) { dp[l][r] = tmp; rt[l][r] = k; } } } } printf("%lld\n", dp[1][n]); dfs(1, n); return 0; }