Luogu 2495 [SDOI2011]消耗战
BZOJ 2286
传说中的虚树经典题。
放上我觉得讲的很好的Luogu置顶题解。 传送门
首先考虑一个暴力的$dp$,设$f_x$表示切断$x$的子树中的所有特殊点且保留$x$的最小代价和,$mn_x$表示$x$到根的路径上最小的一条边的权值。
边界:搜一遍预处理$mn$,对于所有的特殊点$x$, $f_x = mn_x$,其他的点$y$,$f_y = 0$。
转移也比较显然$f_x = \sum_{y} min(mn_y, f_y)$ $y \in son(x)$。
这样子$n^2$的做法就拿到了40分。
我们在$dp$的时候发现其实一次要用的点很少,最多就是所有的特殊点,$1$,还有一些特殊点的$lca$,那么我们可以直接把这些需要的点保留下来重新构造一颗新的树来$dp$,这样子会快很多。
好像这个东西就叫虚树吧。
对于一棵树,我们只要知道了它的欧拉序就可以知道这棵树到底是长什么样的,发现构造出来的这颗新的树上所有点的相对欧拉序都没有变化,而且使用欧拉序还有一个好处,那就是在插入$lca$的时候只要先按照欧拉序排一波序然后按顺序插入就好了,感觉这比很多网上的用栈维护这个$lca$的方法要好很多。
按照所有点的欧拉序排序后模拟系统栈$dp$即可。
时间复杂度似乎是$O(nlogn)$,并不会算。
另外注意每次在弹栈的时候初始化一下就好了,我一开始用$memset$T了。
Code:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N = 2.5e5 + 5; const int Lg = 20; const ll inf = 1LL << 60; int n, qn, tot = 0, head[N], top = 0, sta[N << 2], a[N << 2]; int fa[N][Lg], dep[N], dfsc = 0, in[N], out[N]; ll mn[N], f[N]; bool vis[N]; struct Edge { int to, nxt; ll val; } e[N << 1]; inline void add(int from, int to, ll val) { e[++tot].to = to; e[tot].val = val; e[tot].nxt = head[from]; head[from] = tot; } template <typename T> inline void read(T &X) { X = 0; char ch = 0; T op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } bool cmp(int x, int y) { int dfx = x > 0 ? in[x] : out[-x]; int dfy = y > 0 ? in[y] : out[-y]; return dfx < dfy; } inline ll min(ll x, ll y) { return x > y ? y : x; } void dfs(int x, int fat, int depth, ll nowMn) { fa[x][0] = fat, dep[x] = depth, mn[x] = nowMn; in[x] = ++dfsc; for(int i = 1; i <= 18; i++) fa[x][i] = fa[fa[x][i - 1]][i - 1]; for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; dfs(y, x, depth + 1, min(nowMn, e[i].val)); } out[x] = ++dfsc; } inline void swap(int &x, int &y) { int t = x; x = y; y = t; } inline int getLca(int x, int y) { if(dep[x] < dep[y]) swap(x, y); for(int i = 18; i >= 0; i--) if(dep[fa[x][i]] >= dep[y]) x = fa[x][i]; if(x == y) return x; for(int i = 18; i >= 0; i--) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; return fa[x][0]; } void solve() { int K, cnt; read(K); for(int i = 1; i <= K; i++) { read(a[i]); vis[a[i]] = 1, f[a[i]] = mn[a[i]]; } cnt = K; sort(a + 1, a + 1 + cnt, cmp); for(int i = 1; i < cnt; i++) { int now = getLca(a[i], a[i + 1]); if(!vis[now]) { vis[now] = 1; a[++cnt] = now; } } for(int cur = cnt, i = 1; i <= cur; i++) a[++cnt] = -a[i]; if(!vis[1]) a[++cnt] = 1, a[++cnt] = -1; sort(a + 1, a + 1 + cnt, cmp); top = 0; for(int i = 1; i <= cnt; i++) { if(a[i] > 0) sta[++top] = a[i]; else { int x = sta[top--]; if(x == 1) printf("%lld\n", f[x]); else { int y = sta[top]; f[y] += min(mn[x], f[x]); } f[x] = 0LL, vis[x] = 0; } } } int main() { read(n); for(int x, y, i = 1; i < n; i++) { read(x), read(y); ll v; read(v); add(x, y, v), add(y, x, v); } dfs(1, 0, 1, inf); for(read(qn); qn--; ) solve(); return 0; }