P1040 加分二叉树
知识点: DP,树的结构
原题面
题意简述
一个 \(n\) 个节点的二叉树的中序遍历为 \(1,2,\dots n\)。,每个节点都有一个分数 \(w_i\)。
定义一颗子树的加分为:根的左子树的加分 \(\times\) 根的右子树的加分 \(+\) 根的分数,空子树的加分为 \(1\)。
求二叉树的最高加分,及此时的前序遍历。
\(n\le 30, w_i < 100\)。
分析题意
有个中序遍历的性质:
中序遍历可看做 “拍扁序”,
其每一个子序列 都表示 树联通的一部分。
显然 每一个子序列都满足最优子结构性质。
可求出其最优组成结构,接到其他部分上。
考虑 DP。
设 \(f_{l,r}\) 表示子序列 \([l,r]\) 构成的树的最大加分。
对于空子树的情况,初始化 \(f_{i,i-1} = 1\)。
考虑枚举根 \(k\) 进行转移,则有:
可通过 区间 DP / 记忆化搜索实现。
由于要输出前序遍历,考虑维护 \(root_{l,r}\) 表示最大加分时 \([l,r]\) 的根。
在更新 \(f_{l,r}\) 时顺便维护,输出时递归输出即可。
代码实现
//知识点:DP,树的结构
/*
By:Luckyblock
*/
#include
#include
#include
#define ll long long
const int kMaxn = 30 + 10;
//=============================================================
int n, f[kMaxn], root[kMaxn][kMaxn], val[kMaxn][kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int Dfs(int l, int r) {
if (l > r) return 1;
if (l == r) {
root[l][r] = l;
return f[l];
}
if (val[l][r]) return val[l][r];
for (int rt = l; rt <= r; ++ rt) {
int tmp = Dfs(l, rt - 1) * Dfs(rt + 1, r) + f[rt];
if (tmp > val[l][r]) {
val[l][r] = tmp, root[l][r] = rt;
}
}
return val[l][r];
}
void Print(int l, int r) {
if (l > r) return ;
printf("%d ", root[l][r]);
Print(l, root[l][r] - 1);
Print(root[l][r] + 1, r);
}
//=============================================================
int main() {
n = read();
for (int i = 1; i <= n; ++ i) f[i] = read();
printf("%d\n", Dfs(1, n));
Print(1, n);
return 0;
}