问题描述
蒜头君最近遇到一道难题,想请聪明的你来帮忙解决一下。题目给了一棵奇怪的二叉树,树上有 n 个结点,每个结点按中序遍历的顺序依次编号为 1 到 n。每个结点都有一个权值,第 i 个结点的权值为 w i 。每棵子树也有一个权值,记 s(i) 为以编号 i 为根结点的子树的权值,结点 i 的左孩子结点编号为 i left,右孩子结点编号为 i right,则 s(i) 的计算方法为:s(i)=s(i left)×s(i right)+w i 。需要注意的是,如果结点i 为叶子结点,则s(i)=w i 。如果结点 i 不是叶子结点,但是其左子树为空,则 s(i left)=1;同样,如果结点 i 的右子树为空,则 s(i right )=1。现在需要你来设计一棵二叉树,使得中序遍历得到的结点序列为 1 到 n,且使二叉树的根结点 root 的s(root) 最大。
输入格式
输入有两行。输入第一行是一个整数 n(1≤n≤30),表示二叉树上一共有 n个结点。 输入第二行是 n个整数,每两个整数用一个空格隔开,第 i 个整数对应第 i个结点的权值 w i(1≤w i ≤100)。
输出格式
输出两行。第一行输出一个整数,表示二叉树根结点 root 能获得的最大s(root),结果保证在 int 范围内。第二行输出 n 个整数,每两个整数之间用一个空格隔开。输出二叉树的前序遍历结果。
思路:
中序遍历中,root在中间,左右子树分别在两边。用dp[i][j]表示从i到j的中序遍历表示的子树长度,可从中枚举root,于是dp[i][j]=max(dp[i][j],dp[i][k-1]*dp[k+1][j]+s[k]),转化成石子合并问题。
关于输出,可以记录每一个区间的root节点,然后递归着输出。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=35;
int n,a[N],dp[N][N],root[N][N];
void printf_way(int l,int r){
if(l>r) return ;
printf("%d ",root[l][r]);
printf_way(l,root[l][r]-1); printf_way(root[l][r]+1,r);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++) {
dp[i][i]=a[i]; root[i][i]=i;
}
for(int l=2;l<=n;l++)
for(int i=1,j;i<=n-l+1;i++){
j=i+l-1;
for(int k=i+1;k<j;k++)
if(dp[i][k-1]*dp[k+1][j]+a[k]>dp[i][j]){
dp[i][j]=dp[i][k-1]*dp[k+1][j]+a[k];
root[i][j]=k;
}
if(dp[i+1][j]+a[i]>dp[i][j]){
dp[i][j]=dp[i+1][j]+a[i];
root[i][j]=i;
}
if(dp[i][j-1]+a[j]>dp[i][j]){
dp[i][j]=dp[i][j-1]+a[j];
root[i][j]=j;
}
}
printf("%d\n",dp[1][n]);
printf_way(1,n);
return 0;
}