P1273 有线电视网 (树状背包DP) (2023-02-09)

题目链接: https://www.luogu.com.cn/problem/P1273

题意

给一颗有 n 个节点,其中共有 n-m 个叶子的树。给出经过每一条边所耗费的体力 wi。每个叶子上有一个值 ai,如果能访问到该叶子,那么可以补充 ai 点体力。

你现在从根节点出发,在保证结束后体力值不小于零的情况下,求能够访问到的最多的叶子数。

2≤N≤3000, wi,ai≤10

思路

首先发现这题可以划分成若干个子问题,考虑DP。子问题的划分在树上进行,所以首先它是一个树状DP。

然后就可以写出对于每个子树上的解 dp[u][i] 表示在 u 节点上,访问完 i 个叶子后的剩下体力。

状态转移直接一个分组背包就可以了。非常的简单。

复杂度 O(n^3)

代码

#include <iostream>
#include <vector>
#include <cstring>

#define orep(i,l,r) for(auto i = (l); i < (r); ++i)
#define NL          std::cout << '\n';

const int maxN = 3e3+5;
int arr[maxN], dp[maxN][maxN];
int n, m;
struct st { int to, w; };
std::vector<std::vector<st>> e;

int init(int u = 1) {
  if (u > n-m) { dp[u][1] = arr[u]; return 1; }
  dp[u][0] = 0;
  int user = 0;
  orep(i, 0ul, e[u].size()) {
    int v = e[u][i].to, w = e[u][i].w;
    int cur = init(v);
    user += cur;
    for(int j = user; j > 0; --j) {
      orep(k, 1, 1+std::min(j, cur)) {
        dp[u][j] = std::max(dp[u][j], dp[u][j-k] + dp[v][k] - w);
      }
    }
  }
  return user;
}

int main() {
  std::ios::sync_with_stdio(0); std::cin.tie(0);
  std::cin >> n >> m;
  e = std::vector<std::vector<st>>(n+1);
  orep(i, 0, n-m) {
    int k; std::cin >> k;
    orep(j, 0, k) {
      int t, w; std::cin >> t >> w;
      e[i+1].push_back({t, w});
    }
  }
  memset(dp, -0x3f, sizeof(dp));
  orep(i, n-m+1, n+1) { std::cin >> arr[i]; }
  init();
  for(int i = m; i > 0; i--) {
    if (dp[1][i] >= 0) { std::cout << i; return 0; }
  }
}