题意:
给定二叉树的中序遍历:1、2、3、…、n,我们知道会有不同的形态,题目要求我们求出 所有形态中分值最大的方案(输出其对应的前序遍历)
思路:
我们知道,一棵子树 的 中序遍历 在 整棵树的中序遍历序列中 一定是 连续的一段
dp[l, r] 状态表示
-
集合:当前 以
l
为左端点,r
为右端点的区间 作为 中序遍历,生成树的方案 -
属性:所有方案中的最大分数
dp[l, r] 状态表示(集合划分的依据:根据根节点在区间[L,R]的不同位置进行划分)
我们假设根节点在区间[L,R]
中的第k
个点,那么对于所有形如这样的方案的最大值的求解
当根节点在k
位置,可见 左子树所在区间为[L,k-1]
,右子树所在区间为[k+1, R]
显然,左右子树是相互独立的,两者互不影响
左子树最大值:dp[l, k-1]
,右子树最大值f[k+1, R]
那么 以k为根节点的树的分数为dp[l, k-1] × dp[k+1, R] + 根节点分数w[u]
d p l , r = m a x ( d p l , k − 1 × d p k + 1 , r + w k ) ( l ≤ k ≤ r ) dpl,r=max(dpl,k−1×dpk+1,r+wk)(l≤k≤r) dpl,r=max(dpl,k−1×dpk+1,r+wk)(l≤k≤r)
初始状态: d p l , l = w l dpl,l=wl dpl,l=wl 题设规定只有一个节点的子树分数就是其权值
目标状态: d p l , r dpl,r dpl,r
关于记录方案:
-
在计算每个状态的过程中,用二维数组
g
记录 每个区间的最大值所对应的根节点编号。 -
那么最后就可以通过DFS求出最大加分二叉树的前序遍历了。
关于输出最小字典序的方案:
- dfs的时候只需先尽可能找到最左侧的最大值,即可输出字典序最小的方案
时间复杂度:
O ( n 3 ) O(n^3) O(n3)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 35;
int dp[N][N], g[N][N];//g(l, r)用于记录(l, r)区间内的根结点
int w[N];
int n;
void dfs(int l, int r)//两个参数分别为区间的左右端点
{
if(l > r) return ;//子树为空,不用输出直接返回
int k = g[l][r];//求当前子树的根节点
cout<<k<<' ';//并输出
//前序遍历,根-左-右
dfs(l, k-1);
dfs(k+1, r);
}
int main()
{
cin>>n;
for(int i=1; i<=n; ++i) cin>>w[i];
for(int i=1; i<=n; ++i) dp[i][i] = w[i], g[i][i] = i;
//叶结点的权值是本身,这是题目规定的
//初始化dp和g
//当然以上可以写进循环里,特判len==1的情况,这样写便于理解
for(int len=2; len<=n; ++len)//区间长度
{
for(int l=1; l+len-1<=n; ++l)//左端点
{
int r = l + len - 1;//右端点
for(int k=l; k<=r; ++k)//决策根节点的位置
{
int left = k==l ? 1 : dp[l][k-1];//如果没有左子树则left置为1
int right = k==r ? 1 : dp[k+1][r];//如果没有右子树则right置为1
int score = left * right + w[k];//根据题目给出的式子计算分值
if(score > dp[l][r])
{
dp[l][r] = score;//分值更大时,更新状态
g[l][r] = k;//记录决策根节点 k
}
}
}
}
cout<<dp[1][n]<<'\n';
dfs(1, n);
return 0;
}