分析:
条件/性质:
- 中序遍历 1,2,3...n
- 左右子树为空 ,积分=1
- 子树的积分=左*右+根
根据中序遍历是1,2,3...n,可以分为以下的情况
根结点=k,左边区间全部为l,右边区间全部为r,左右区间相互独立互不干扰
那么,就可以转换为区间dp,石子合并的模型,寻找中断点作为根结点
需要注意是子树为空的情况,子树为空时需要特殊考虑
状态表示:dp[i][j]表示所有中序遍历顺序为i~j的集合
递推顺序:1<=len<=n (可能只有1个结点)
初始化:全为0
边界:dp[i][i]=w[i]
状态计算/子集划分:
由于需要枚举[i~j]集合中谁来当根结点,可能有三种情况
- 只有左子树 j作为根结点,左子树为1
- 只有右子树 i作为根结点,右子树为1
- 有左右子树 枚举[i+1,j-1]中的一个k作为根结点
根据当前树的左右子树和枚举根结点,可以划分为三个集合
- 左子树dp[l][k-1] + 根结点w[k] + 右子树dp[k+1][j]
根据中断点的不同,就可以不重不漏的划分为r-l+1个子集,具体=1个无右子树+1个无左子树+多个有左右子树的集合
对于需要求前序遍历的结果,前序遍历=根左右,所以只要枚举出每次都是选择哪个作为根结点就可以了,也就是记录每次最优决策都选择了什么
#include <iostream>
#include <algorithm>
using namespace std;
const int N=54;
int dp[N][N];
int w[N];
int g[N][N];
void dfs(int l,int r)
{
if(l>r) return ;
int p=g[l][r];
cout<<p<<' ';
dfs(l,p-1);
dfs(p+1,r);
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>w[i];
for(int len=1;len<=n;len++)
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
if(len==1)
dp[i][j]=w[i],g[i][j]=i; //默认用自己作为根结点
else
for(int k=i;k<=j;k++)
{
int left = k==i?1:dp[i][k-1];
int right = k ==j?1:dp[k+1][j];
int t=left*right+w[k];
if(t>dp[i][j])
{
dp[i][j]=t;
g[i][j]=k;
}
}
}
cout<<dp[1][n]<<endl;
dfs(1,n);
return 0;
}