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\) 进行转移,则有:

\[f_{l,r} = \max_{k=l}^{r}\{f_{l,k-1} \times f_{k+1,r} + val_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;
}