【NOIP】加分二叉树
题目描述
设一个n个节点的二叉树tree的中序遍历为(1,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第ii个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数。
若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree的最高加分
(2)tree的前序遍历
输入格式
第1行:1个整数n(n<30),为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数(分数 <100)。
输出格式
第1行:1个整数,为最高加分(0≤4,000,000,000)。
第2行:n个用空格隔开的整数,为该树的前序遍历。
输入输出样例
输入 #1 复制
5
5 7 1 2 10
输出 #1 复制
145
3 1 2 4 5
分析
首先,这里请允许我灵魂画图一波。。。。。。。(这个图是随便画的,不用看仔细。。。。。)
题目: 这里其实我们可以看成是,根 + 左子树最大值 * 右子树最大值 。(应该是废话。。。。。。。)
我们可以从左到右,让每个数都是根,进行一次dfs 求 左子树最大值 * 右子树最大值 + 根值
for(int i=L;i<R;i++){
long long t = dfs(L,i-1) * dfs(i+1,R) + a[i];
然后每次max 比下(如果这次的数值更大就保存这次的数值 和根节点)根节点用数值root【】【】存放,
还要一个dp【】【】,每次 dfs结束,就把这次的dp【】【】结果存入,那么下次再遇到就直接放回这个数值,这是非常重要的
否则会超时
这里有一个问题,就是我们会dfs时遇到 dfs(L,R) ,然后L > R 。这样无疑是dfs无结果的,那就返回1;(题目要求:若某个子树为空,规定其加分为1)。
最后,题目还要输出这个树的中序。。。上面我们已经得到跟节点,那就用一个递归输出中序。。。。(这里过程不用说了把)
代码
#include<iostream>
#include<cstdio>
using namespace std;
int n,a[40],root[40][40];
long long dp[40][40];
long long dfs(int L,int R){
if(L>R) return 1; //因为要左右子节点相乘,所以如果是空则给1
if(dp[L][R]) return dp[L][R]; //关键一步,记忆化,以前走过那就直接返回结果
long long maxn = 0;
for(int i=L;i<R;i++){
long long t = dfs(L,i-1) * dfs(i+1,R) + a[i];
if(t > maxn){
maxn = t;
root[L][R] = i;
}
}
return dp[L][R] = maxn;
}
void dg(int L,int R){
if(L>R) return ; //防止出现 L >R
printf("%d ",root[L][R]);
dg(L,root[L][R]-1);
dg(root[L][R]+1,R);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
dp[i][i] = a[i];
root[i][i] = i;
}
printf("%lld\n",dfs(1,n));
dg(1,n);
return 0;
}