基础树形DP——P1040 加分二叉树

时间:2019-08-20
本文章向大家介绍基础树形DP——P1040 加分二叉树,主要包括基础树形DP——P1040 加分二叉树使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目描述

设一个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个整数,为最高加分(Ans≤4,000,000,000)。

第2行:n个用空格隔开的整数,为该树的前序遍历。

解法

这个题目的一个重要条件是你求解的二叉树的中序遍历是(1,2,3...n)。在二叉树的中序遍历中,对于任意一个点i,它前面的数一定都在它的左子树中, 它右边的数可能在它的右子树中(挺显然的吧)。因此中序遍历区间[1,i]一定是以某个\(a_j\)(j∈[1,i])为根的子树(还算显然吧).

因此我们可以对于每一段区间试着选出一个根,然后算出以它为根的最大值,最后记录答案.

具体来说:

\(f_{i,j}\)表示以点\([i,j]\)组成一棵二叉树的最大加分,接下来我们来枚举断点k.这个断点事实上代表了树根,即我们令\(i\)\(k-1\)是左子树,\(k+1\)\(j\)是右子树,然后我们递归下去,退回以后更新答案,枚举下一个\(k\).最终目标:\(f_{1,n}\).过程中如果记录过答案就直接返回记录的值,也就是应用树形DP常用的做法:记忆化搜索.

代码:

int dfs(int l, int r) {
    if (l > r) return 1;//不合法判断
    if (l == r) {//递归到叶子节点
        root[l][r] = l;//以它自己为根
        return val[l];
    }
    if (maxinum[l][r]) return maxinum[l][r];//记忆化的关键
    int k, ans = -1000000000;
    for (int i = l; i <= r; ++i) {
        int temp = dfs(l, i - 1) * dfs(i + 1, r) + val[i];//枚举了断点,递归下去更新答案
        if (temp > ans) {//更新答案
            ans = temp;
            k = i;
        }
    }
    root[l][r] = k;//记录最合适的根
    maxinum[l][r] = ans;//记录最大答案
    return ans;
}

这样我们就做完了第一问

别忘了,这题还有第二问[允悲]
第二问要求我们输出前序遍历,也就是先输出根,后输出左儿子,后输出右儿子.其实我们在第一问中已经得到了这个问题的答案...左儿子,也就是左子树的根,右儿子,也就是右子树的根.我们选完根,第二问就已经得到答案了.我们只需要从\(root_{1,n}\)开始,先递归\(root_{1,root_{1,n}-1}\)再递归\(root_{root_{1,n}+1,n}\),然后以此类推就可以了.

可能上面说的有点绕,具体看代码吧:

void findroot(int l, int r) {
    if (l > r) return;
    cout << root[l][r] << " ";
    findroot(l, root[l][r] - 1);
    findroot(root[l][r] + 1, r);
    return;
}

是不是很简短呢?

哦对了,别忘了开long long.

原文地址:https://www.cnblogs.com/i-cookie/p/11383913.html