题目

题目描述:
设一个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. 首先,这道题的中序遍历是不重复且有序的,所以我们一下就想到了二叉排序树
  2. 然后这道题我们的根节点是由左子树和右子树的加分项加上自己的分数组成的
  3. 所以这一点我们只要注意观察了就能发现:左子树和右子树,对于编号来讲,都是一个连续的序列
  4. 而且,左子树的区间 + 根节点位置 + 右子树的区间,也是一个连续的区间

那么在上面的看题目中我们发现了:
  • 每一个连续的区间可以单独求出一个加分项值,按此思想,我们当然也能求出这个区间里面的最大加分项值。
  • 这就是我们的区间dp了。

然后我们扯出了dp思想,我们还是熟悉的这句话:
  • 动态规划最重要的就是递推和dp数组含义

区间dp:
  1. 这道题又牵扯到了一个我不会的玩意儿:区间dp。不同种类的dp其实最主要的就是dp数组的含义不同,剩下的都是递推。
  2. 区间dp的核心就是dp数组的含义是:dp[l][r] = 条件值。(l是区间左端点,r是区间右端点)
  3. 然后这道题呢,我们的dp数组的含义是什么呢?我们在看题目时就已经明白了:这里dp数组保存的是一个连续区间的最大加分值(dp[l][r] = 最大加分值)

既然我们已经知道dp的含义了,下面我们就来进行操作吧:
  1. 首先呢,我们区间有两个要点:区间左端点位置 / 区间右端点位置
  2. 而在本题中呢,我们dp当然要从底层开始:也就是先把区间短的dp数组完成,再用来求更大的dp(就是先构建dp的底层结构,否则数组不全就会出错)。
  3. 所以我们就不用循环遍历 l 和 r 的位置了,我们循环遍历 len 和 l (区间长度和区间左端点,这样可以计算出 r 的位置,也能从底层开始计算了。
  4. 然后要判断哪一种加分最大,我们只要让区间内每一个都当一次根节点就好了,然后选出最大值(同时要记录最大时的根节点用于输出前序遍历)。

前序遍历:
  1. 前序遍历咋整呢?我们一般都是树结构来弄的。所以我们这里首先要知道前序遍历的特点:先输出根节点,然后在去找左孩子,然后是右孩子。
  2. 既然是先出根节点就好办了,我们只要知道每一个区间内的根节点,然后输出根节点,再去遍历左区间,然后是右区间
    (就相当于我们的中序遍历序列,知道了前序遍历的根节点位置,那么找出来然后递归输出不就好了?这就是我上面让大家求出区间根节点位置的原因)。

打代码吼:
  1. 首先保存变量和数组(每个点的点权)。
  2. 然后按照我上面的操作过程来,构建好dp数组和root(最大情况根节点)数组。
  3. 因为区间范围就是1~n,所以输出dp[1][n]就是最大加分和了。
  4. 接下来按照我讲的进行中序遍历的前序查找就好了。
  5. 代码挺详细der,那就详情看代码趴~


AC代码

#include <iostream>
using namespace std;
typedef long long ll;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//代码预处理区

const int MAX = 37;
int w[MAX];
ll dp[MAX][MAX], root[MAX][MAX];//dp是i到j区间的最大加分,root是i到j最大加分情况下的根节点位置
//全局变量区

void dfs_print(int l, int r);
//函数预定义区

int main() {
    IOS;
    int n; cin >> n;
    for (int i = 1; i <= n; i++) cin >> w[i];
    for (int len = 1; len <= n; len++)
    //区间长度
        for (int l = 1; l <= n; l++) {
        //区间左端点
            int r = l + len - 1;//区间右端点
            for (int k = l; k <= r; k++) {
            //区间决策点(判断是否是最大根)
                ll lchild = l == k ? 1 : dp[l][k - 1];//左孩子加分
                ll rchild = r == k ? 1 : dp[k + 1][r];//右孩子加分
                ll score = l == r ? w[k] : lchild * rchild + w[k];
                //当前根节点的总分,特判无孩子的情况,否则会多1
                if (score > dp[l][r]) {
                    dp[l][r] = score;
                    root[l][r] = k;
                }
                //分大的话就替换
            }
        }
    cout << dp[1][n] << endl;
    dfs_print(1, n);
    cout << endl;
    //输出最大加分与最大加分的前序遍历
    return 0;
}
void dfs_print(int l, int r) {
    if (l > r) return;
    int mid = root[l][r];//最大根节点
    cout << mid << " ";
    dfs_print(l, mid - 1);
    dfs_print(mid + 1, r);
}
//函数区