BZOJ2286 消耗战

末鹿安然 提交于 2019-12-01 08:24:59

 

[传送门]

如果只有单次询问,可以直接树形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;
}
View Code

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!