[ABC311Ex] Many Illumination Plans 题解

Description

给定一棵根节点为 \(1\) 的有根树 \(T\),树中共有 \(N\) 个节点,编号从 \(1\)\(N\)。对于每个顶点 \(i\)\(2 \leq i \leq N\)),其父节点是 \(P_i\)。每个节点都有两个非负整数属性,分别称为美丽值重量。节点 \(i\) 的美丽值为 \(B_i\),重量为 \(W_i\)。此外,节点被涂上红色或蓝色,其颜色用整数 \(C_i\) 表示:当 \(C_i=0\) 时,节点 \(i\) 为红色;当 \(C_i=1\) 时,为蓝色。

对于每一个节点 \(v\),定义函数 \(F(v)\) 表示以下问题的解:

从树 \(T\) 中提取出以 \(v\) 为根的子树,称之为 \(U\)。对 \(U\) 可以进行若干次以下操作(操作不会改变未删除节点的属性):

  • 选择一个非根节点 \(c\),设 \(c\) 的父节点为 \(p\)
  • 对于所有与 \(c\) 相连的边:
    • 假设边的另一端是 \(u\),删除这条边,然后用一条以 \(p\) 为父节点的新边连接 \(p\)\(u\)
  • 删除节点 \(c\) 和边 \(p,c\)

最终的 \(U\) 满足以下条件即为好的有根树

  • \(U\) 中相连节点的颜色不同。
  • 所有节点的重量总和不超过 \(X\)

找出在所有可能的好的有根树中,节点美丽值总和的最大值。

请输出每个节点 \(v\) 对应的 \(F(v)\),即 \(F(1), F(2), \dots, F(N)\)

\(2\leq N\leq 200,0\leq X\leq 50000\)

Solution

首先这题有个很简单的想法是设 \(f_{u,s,0/1}\) 表示 \(u\) 的子树,选的总重量为 \(s\),最上面的那些点的颜色为 \(0/1\) 的最大美丽度之和,暴力转移就是 \(O(nX^2)\)

注意到如果按照上面的做法从下往上转移,则无法规避掉单次 \(O(X^2)\)\(\min\) 加卷积,所以考虑从上往下 dp,将 dp 数组合并改为往 dp 中插入元素。

先考虑固定子树的根怎么做。

对于当前的点 \(u\),由于儿子之间是平等的,所以有个很巧妙的想法是往 dfs 的状态里加入一个数组 \(f\),表示这个子树里面的选择需要在 \(f\) 的基础上更新,然后 dfs 的返回结果改成两个大小为 \(x+1\) 的数组,分别表示 \(u\) 的子树内选的最浅的点颜色为 \(0/1\),且在 \(f\) 的基础上更新的结果数组。

由于 \(u\) 的儿子中有一个儿子可以只遍历一次,其余都需要两次,容易想到先遍历重儿子,得到只有重儿子子树的 \(f_0\)\(f_1\)。然后对于其它儿子 \(v\),分别调用 \(f_0\leftarrow \text{dfs}(v,f_0)[0],f_1\leftarrow\text{dfs}(v,f_1)[1]\)

这个东西的复杂度是 \(\displaystyle T(sz_u)=T(sz_{son_u})+2\sum_{v\neq son_u}{T(sz_v)}+O(X)\leq 3\cdot T(sz_u/2)+O(X)=O(n^{\log_23}X)=O(n^{1.59}X)\)

暴力对于每个子树做这件事情就是 \(O(n^{2.59}X)\),过不了。


考虑优化。

可以感受到上面的做法还是有些浪费,因为如果要求以 \(u\) 为根的答案,这个需要用到 \(son_u\) 从零开始的 dp 结果,暴力枚举根的化这个 \(son_u\) 的 dp 结果就会算两次,很不优。

所以可以往 dfs 的状态中加一维 \(op\) 表示是否需要从零开始计算 \(u\) 子树里的答案,即 \(\text{dfs}(u,f,0/1)\)

如果 \(op=0\),则按照上面的做法转移即可。如果 \(op=1\),则对于轻儿子全都从零开始计算,\(u\) 不继承这些结果。然后 \(u\) 先继承重儿子的结果,对于轻儿子和 \(op=0\) 的做法一样。

可以证明时间复杂度仍然是 \(O(n^{1.59}X)\)

时间复杂度:\(O(n^{1.59}X)\)

Code

#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 205;

int n, x;
int p[kMaxN], b[kMaxN], w[kMaxN], c[kMaxN], res[kMaxN];
int sz[kMaxN], wson[kMaxN];
std::vector<int> G[kMaxN];

inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }

void dfs1(int u) {
  sz[u] = 1;
  for (auto v : G[u]) {
    dfs1(v);
    sz[u] += sz[v];
    if (sz[v] > sz[wson[u]]) wson[u] = v;
  }
}

std::array<std::vector<int>, 2> dfs2(int u, std::vector<int> now, bool op) {
  if (op) {
    for (auto v : G[u]) {
      if (v != wson[u]) dfs2(v, now, op);
    }
  }
  std::array<std::vector<int>, 2> f = {now, now};
  if (wson[u]) f = dfs2(wson[u], now, op);
  for (auto v : G[u]) {
    if (v == wson[u]) continue;
    f[0] = dfs2(v, f[0], 0)[0];
    f[1] = dfs2(v, f[1], 0)[1];
  }
  if (op) {
    for (int i = x; i >= w[u]; --i) {
      if (i >= w[u]) chkmax(res[u], f[c[u] ^ 1][i - w[u]] + b[u]);
    }
  }
  for (int i = x; i >= w[u]; --i) chkmax(f[c[u]][i], f[c[u] ^ 1][i - w[u]] + b[u]);
  return f;
}

void dickdreamer() {
  std::cin >> n >> x;
  for (int i = 2; i <= n; ++i) std::cin >> p[i], G[p[i]].emplace_back(i);
  for (int i = 1; i <= n; ++i) std::cin >> b[i] >> w[i] >> c[i];
  dfs1(1);
  std::vector<int> now(x + 1, -1e18);
  now[0] = 0;
  dfs2(1, now, 1);
  for (int i = 1; i <= n; ++i) std::cout << res[i] << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2025-08-25 20:12  下蛋爷  阅读(14)  评论(0)    收藏  举报