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;
}
View Code

 

posted @ 2018-09-25 13:04  CzxingcHen  阅读(125)  评论(0编辑  收藏  举报