题目描述
设一个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.