\(\quad\) \(n\) 个点,\(n - 1\) 条边,显然就是一棵树了。题干说“可选择不获取资源”,但由于获取资源不需要时间,那显然必须要获取。
\(\quad\) 发现数据范围比较小,考虑多维 dp。设计如下:
\(\qquad\) \(\bullet\) 设 \(dp_{i,j,k}\) 表示从 \(i\) 出发,花费 \(k\) 单位时间到达 \(j\) 的最大收益。
\(\qquad\) \(\bullet\) \(dp_{i,j,k} = dp_{i,p,t} + dp_{p,j,k-t}\)
\(\quad\) 这样最多要转移 \(2.73 \times 10^{12}\) 次,显然超时。主要原因是状态设计得太过具体、暴力。
\(\qquad\) \(\bullet\) 设 \(f_{i,j}\) 表示花费 \(j\) 单位时间,从 \(i\) 出发,经过 \(i\) 的子树,最终返回 \(i\) 的最大收益;\(g_{i,j}\) 表示花费 \(j\) 单位时间,从 \(i\) 出发,最终到达 \(i\) 的子树的最大收益;\(h_{i,j}\) 表示花费 \(j\) 单位时间,从 \(i\) 的子树中出发,经过 \(i\),最终到达 \(i\) 的子树的最大收益。
\(\qquad\) \(\bullet\) 事实上,\(g_{i,j}\) 不但可以表示“花费 \(j\) 单位时间,从 \(i\) 出发,最终到达 \(i\) 的子树的最大收益”,还可以表示“花费 \(j\) 单位时间,从 \(i\) 的子树出发,最终到达 \(i\) 的最大收益”。
\(\quad\) 上述说明中的所有出发和到达都指:消灭了出发至到达路径上的所有敌人。个人感觉这道题还是相当复杂的,转移的具体描述和配图以后再补,可以先看代码试着理解。
#include <cstdio> inline int read(void){ static int res; res = 0; static char ch; ch = std::getchar(); while(ch < '0' || ch > '9') ch = std::getchar(); while(ch >= '0' && ch <= '9') res = res * 10 + ch - 48, ch = std::getchar(); return res; } inline int max(const int& a, const int& b){ return a > b ? a : b; } const int MAXN = 3e2 + 19; struct Edge{ int to, next, dist; }edge[MAXN << 1]; int cnt, head[MAXN]; inline void add(int from, int to, int dist){ edge[++cnt].to = to; edge[cnt].dist = dist; edge[cnt].next = head[from]; head[from] = cnt; } int n, T; int w[MAXN], t[MAXN]; int a, b, c; int f[MAXN][MAXN], g[MAXN][MAXN], h[MAXN][MAXN]; int ans; void dfs(int node, int fa){ static int tmp[3]; if(t[node] >= T) return; for(int i = head[node]; i; i = edge[i].next) if(edge[i].to != fa){ dfs(edge[i].to, node); for(int j = T; j >= t[node]; --j){ for(int k = j - edge[i].dist; k >= t[node]; --k){ tmp[0] = f[node][k], tmp[1] = g[node][k], tmp[2] = h[node][k]; int rest = j - k - edge[i].dist; if(rest >= t[edge[i].to]){ g[node][j] = max(g[node][j], tmp[0] + g[edge[i].to][rest]); h[node][j] = max(h[node][j], tmp[1] + g[edge[i].to][rest]); } rest -= edge[i].dist; if(rest >= t[edge[i].to]){ g[node][j] = max(g[node][j], tmp[1] + f[edge[i].to][rest]); f[node][j] = max(f[node][j], tmp[0] + f[edge[i].to][rest]); h[node][j] = max(h[node][j], tmp[2] + f[edge[i].to][rest]); h[node][j] = max(h[node][j], tmp[0] + h[edge[i].to][rest]); } } ans = max(ans, h[node][j]); } } } int main(){ n = read(), T = read(); for(int i = 1; i <= n; ++i) w[i] = read(); for(int i = 1; i <= n; ++i){ t[i] = read(); if(t[i] <= T) f[i][t[i]] = g[i][t[i]] = h[i][t[i]] = w[i]; } for(int i = 1; i < n; ++i){ a = read(), b = read(), c = read(); add(a, b, c), add(b, a, c); } dfs(1, 0); std::printf("%d\n", ans); return 0; }
\(\quad\) 其实只要出现了树形结构,就要试着考虑树形 dp,不要再去想奇奇怪怪的转移方程了。树形 dp 的特点:
\(\qquad\) \(\bullet\) 状态与单个节点有关,每个节点的状态都一定可以由子节点转移。
\(\qquad\) \(\bullet\) 转移与 dfs 结合。
\(\quad\) 在树形 dp 中,如果给定的图的边都是自上而下的,一定可以连有向边,最上面的点为根。反之,连无向边,随意选取根节点(有时不选 \(1\) 当根会被卡)。
本文系参考 1773:大逃杀 而成。
来源:https://www.cnblogs.com/natsuka/p/12298470.html