区间DP——加分二叉树
这道题目给了我们中序遍历,在这里我们需要用到一个结论:
一颗子树的中序遍历在给定的中序遍历中,一定是连续的一段。
因此,这里我们进行状态表示:
d p [ L , R ] dp[L,R] dp[L,R]表示所有中序遍历是 [ L , R ] [L,R] [L,R]这一段的二叉树的集合、
对于状态计算,由于一段中序遍历可能会对应多个不同的二叉树,因此,我们在这里比较容易区分的最后一个不同点就是每颗子树的根,也就是,我们按照根节点的不同对集合进行划分。
对于 d p [ L , R ] dp[L,R] dp[L,R]这个集合来说,可以分成以L为根节点,以L + 1为根节点……,以R为根节点这么多不同的集合划分。因此,对于 [ L , R ] [L,R] [L,R]我们可以枚举根节点,确定出最大值。
假设我们现在枚举的根节点是K,那么该树的最大值就是:
d p [ L , K − 1 ] ∗ d p [ K + 1 , R ] + w [ K ] dp[L,K-1]*dp[K+1,R]+w[K] dp[L,K−1]∗dp[K+1,R]+w[K]
也就是两个子树的最大值的积再加上当前此根节点的权值。这样问题其实就已经分解成了区间DP问题。下面一个重要的问题就是怎么能够把当前根节点进行记录,也就是记录方案。
我们采用一个数组进行存储。用 r o o t [ L , R ] root[L,R] root[L,R]来记录当前这个区间我们选取的根节点。
我们首先确定了 r o o t [ 1 , N ] root[1,N] root[1,N],这样我们可以递归处理左右子树,分别找出左右子树选取的根节点。
#include <bits/stdc++.h>
using namespace std;
int w[35], root[35][35], dp[35][35];
void dfs(int l, int r) {
if (l > r) return;
cout << root[l][r] << " ";
// 由于输出的是前序遍历,递归输出左右子树选取的根节点
dfs(l, root[l][r] - 1);
dfs(root[l][r] + 1, r);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
for (int len = 1; len <= n; len++) {
for (int l = 1; l + len - 1 <= n; l++) {
int r = l + len - 1;
if (l == r) { // 叶子节点
dp[l][r] = w[l];
root[l][r] = l;
}
else {
for (int k = l; k <= r; k++) {
int left = k == l ? 1 : dp[l][k - 1]; // 左子树的最大值
int right = k == r ? 1 : dp[k + 1][r]; // 右子树的最大值
if (dp[l][r] < left * right + w[k]) { // 保证字典序最小
dp[l][r] = left * right + w[k];
root[l][r] = k;
}
}
}
}
}
cout << dp[1][n] << endl;
dfs(1, n);
return 0;
}