加分二叉树【树形dp】

设一个n个节点的二叉树tree的中序遍历为(l, 2, 3, …, n),其中数字 1, 2, 3, …, n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:

  • subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
  • 若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为1, 2, 3, …, n) 且加分最高的二叉树tree。要求输出;

  1. tree的最高加分
  2. tree的前序遍历

我们注意到给出的数据是中序遍历,对于中序遍历, 在根的左右两侧,恰好是他的左右两棵子树的序列

我们只要枚举区间内的根节点即可

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
typedef long long ll;
using namespace std;
const int maxn = 50;
int n, v[maxn], f[maxn][maxn], root[maxn][maxn];
//root[i][j]表示区间 i - j 内的根节点
//f[i][j] 表示 i - j 的最大加分
void print(int l, int r){//前序输出区间[l, r]
    if(l>r) return;
    if(l==r){
        printf("%d ", l);
        return;
    }
    printf("%d ", root[l][r]);
    print(l, root[l][r]-1);
    print(root[l][r]+1, r);
}
int main(){
    scanf("%d", &n);
    for(int i=1; i<=n; i++) scanf("%d", &v[i]);//读入分数
    for(int i=1; i<=n; i++) f[i][i] = v[i],f[i][i-1] = 1, root[i][i]=i;//初始化,f[i][i-1]表示空子树
    for(int len=1; len<n; len++){//枚举区间长度
        for(int i=1; i+len<=n; i++){//枚举起点 i
            int j = i+len;        //终点 j
            f[i][j] = f[i + 1][j] + f[i][i];//初始化假设左子树为空
            root[i][j] = i; //从左子树为空开始
            for(int k=i+1; k<j; k++){//枚举区间内根节点
                if(f[i][j]<f[i][k-1]*f[k+1][j]+f[k][k]){
                    f[i][j] = f[i][k-1]*f[k+1][j]+f[k][k];
                    root[i][j] = k;
                }
            }
        }
    }
    printf("%d\n", f[1][n]);
    print(1, n);
    return 0;
}

 

posted @ 2020-04-05 18:25  poozhai  阅读(180)  评论(0编辑  收藏  举报