题目链接
题目梗概
我们知道中序遍历相同的二叉树可能有很多种,请在满足给定的中序遍历的二叉树中,找出得分最小二叉树,输出它的得分和前序遍历。
得分规则:树的得分 = 左子树的得分 * 右子树的得分 + 根节点的得分。
特别的,若没有左(右)子树,则左(右)子树得分为1。
若为叶子节点,则得分就是叶子节点的分数。
注:题目中给出的中序遍历是每个结点上的分数,并非结点编号,输入序号即为结点编号。
需要输出的前序遍历中,要的是结点编号,而非结点分数。
解题思路
搜索所有可能的二叉树,得到最优的结果。
搜索问题
我们知道中序遍历是由三部分组成,即左子树,根结点,右子树。当我们选择了根节点之后,左边的就是左子树的中序遍历,右边的就是右子树的中序遍历。
如果我们不考虑左子树,右子树它们各自的内部结构,那么对于一棵二叉树而言,把根节点的所有可能都搜索一边,那就相当于把这棵树所有可能都搜索一边。借用递归思想,我们分别对左右子树进行相同的操作,那最终就可以真正得把所有可能的二叉树都搜索完毕。
记忆化搜索问题
这里建立dp数组,就是把当前情况计算后的结果存储下来,当下一次再遇到同样情况时,可以直接获取结果,避免重复搜索计算。
例如对于总序列[1,2,3,4,5]
中的子序列[1,2]
。
我们会在以3为一级根节点时,[1,2]
会被作为一级左子树计算一遍。
当我们以4作为一级根节点时,一级左子树成为了[1,2,3]
,如果此时我们再以3作为二级根节点,[1,2]
会被作为二级左子树再被计算一遍。
这里的作为二级左子树的[1,2]
和上面作为一级左子树的[1,2]
,在计算结果上显然没有什么不同。这种情况就是我们所说的重复情况。
前序遍历
在上面的搜索计算的过程中,我们可以得到在序列区间[a,b]
n内让哪个结点作为根节点为最优,我们记录下来,最后再利用递归方法,把前序序列打印出来。
完整代码
#include <iostream>
using namespace std;
int n;
int d[30],sp[30][30] = {0};
unsigned dp[30][30] = {0};
unsigned int dfs(int begin, int end){
//[begin,end]是当前树的中序序列的存储范围
if(begin > end) return 1;
if(begin == end){
return d[begin];
}
if(dp[begin][end] != 0) return dp[begin][end];
unsigned int _max = 1, tmp, node;
//遍历根节点的所有可能
for(int i = begin;i<=end;i++){
tmp = dfs(i+1, end) * dfs(begin, i-1)+ d[i];
if(tmp > _max){
_max = tmp;
node = i;
}
}
sp[begin][end] = node;
dp[begin][end] = _max;
return _max;
}
void print(int begin, int end){
if(begin > end) return;
if(begin == end){
cout << begin + 1 << " ";
return;
}
int node = sp[begin][end];
cout << node + 1 << " ";
print(begin,node-1);
print(node+1,end);
}
int main(){
cin >> n;
for(int i = 0;i<n;i++) cin >> d[i];
cout << dfs(0, n-1) << endl;
print(0, n-1);
return 0;
}
来源:CSDN
作者:Vrainy
链接:https://blog.csdn.net/Vrainy/article/details/104348583