BZOJ2286 消耗战
[传送门]
如果只有单次询问,可以直接树形DP
$f\left[u\right]$表示以$u$为根的子树中所有资源丰富的岛屿不与$u$联通的最小代价
转移方程显然
若儿子节点$v$为资源丰富的岛屿
$f\left[u\right] = f\left[u\right] + w\left[u, v\right]$
若儿子节点$v$不是资源丰富的岛屿
$f\left[u\right] = f\left[u\right] + min\left(w\left[u, v\right], f\left[v\right]\right)$
这样下来时间复杂度是$O(n\times q)$。
但是可以发现,资源丰富的点的总和是与$n$同阶的,也就是询问很多的情况下,其实这些关键点是很少的,整棵树我们是没有必要遍历一遍的,那么就引入虚树(Virtual tree)这个概念。
其实就是把关键点和关键点之间的LCA保存下来,被删掉的点之间边的信息用某种方式保留下来,比如这道题中被删掉的点之间的边权,用取$min$的方式保存在保留下来的边中,因为若一条链上没有关键点,其叶子才是关键点,那么中间这条长链选择删哪条边都可以,为了使代价最小,显然删权值最小的边。
建树就是板子了。维护以dfs序为权值的单调栈,pop的过程连边,发现LCA未在栈里及时加进去即可。详见[OI Wiki - 虚树]
其实更多考的是树形DP吧。
#include <bits/stdc++.h> #define ll long long using namespace std; template<typename T> inline void read(T &x) { x = 0; T f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); } x *= f; } const int INF = 0x3f3f3f3f; const int N = 250000 + 7; const int sz = 18; struct E { int v, ne, c; } e[N << 1]; int head[N], cnt, n, m, k; int mn[N][19], fa[N][19], dep[N], st[N], top, h[N]; int dfn[N], id, edge; ll f[N]; bool is[N]; void add(int u, int v, int c) { e[++cnt].v = v; e[cnt].c = c; e[cnt].ne = head[u]; head[u] = cnt; } void dfs(int u, int pre) { dep[u] = dep[pre] + 1, fa[u][0] = pre; dfn[u] = ++id; for (int i = 1; i <= sz; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; mn[u][i] = min(mn[u][i - 1], mn[fa[u][i - 1]][i - 1]); } for (int i = head[u]; i; i = e[i].ne) { int v = e[i].v; if (v == pre) continue; mn[v][0] = e[i].c; dfs(v, u); } } int Lca(int u, int v) { edge = INF; if (dep[u] < dep[v]) swap(u, v); int dif = dep[u] - dep[v]; for (int i = sz; ~i; i--) if (dif >> i & 1) edge = min(mn[u][i], edge), u = fa[u][i]; if (u == v) return u; for (int i = sz; ~i; i--) if (fa[u][i] != fa[v][i]) { edge = min(edge, min(mn[u][i], mn[v][i])); u = fa[u][i], v = fa[v][i]; } edge = min(edge, min(mn[u][0], mn[v][0])); return fa[u][0]; } inline bool cmp(const int &a, const int &b) { return dfn[a] < dfn[b]; } void DP(int u) { f[u] = 0; for (int i = head[u]; i; i = e[i].ne) { int v = e[i].v; DP(v); if (is[v]) f[u] += e[i].c; else f[u] += min((ll)e[i].c, f[v]); } } int main() { read(n); for (int i = 1; i < n; i++) { int u, v, c; read(u), read(v), read(c); add(u, v, c); add(v, u, c); } dfs(1, 0); read(m); while (m--) { read(k); for (int i = 1; i <= k; i++) read(h[i]), is[h[i]] = 1; sort(h + 1, h + k + 1, cmp); st[top = 1] = 1, head[1] = 0, cnt = 0; for (int i = 1; i <= k; i++) { int lca = Lca(st[top], h[i]); if (lca != st[top]) { while (dfn[lca] < dfn[st[top - 1]]) { Lca(st[top - 1], st[top]); add(st[top - 1], st[top], edge); top--; } if (dfn[lca] > dfn[st[top - 1]]) { head[lca] = 0; Lca(lca, st[top]); add(lca, st[top], edge); st[top] = lca; } else { Lca(lca, st[top]); add(lca, st[top--], edge); } } head[h[i]] = 0; st[++top] = h[i]; } for (int i = 1; i < top; i++) Lca(st[i], st[i + 1]), add(st[i], st[i + 1], edge); DP(1); printf("%lld\n", f[1]); for (int i = 1; i <= k; i++) is[h[i]] = 0; } return 0; }