虚树——P2495 [SDOI2011]消耗战

好久没有学习新的知识了。
https://www.luogu.com.cn/problem/P2495
今天我学习了一下虚树。
虚树就是一个不存在的树。用来简化问题。它只包括一些关键点和他的LCA
我们来看看上面这一道题

题意

给出一棵树,含有边权。
每次询问给出k个点,然后询问每个点与根断开的最小代价。

做法

因为数据的关系肯定没法直接dp过去的。
首先我们可以发现可以与根到其他点的路径没有关系,所以只要保存询问点到根以及lca的信息就可以了。那么这就是虚树了。
关于虚树的构造,网上有很多博客写得不错,我就不讲了。
下面讲怎么虚树上处理dp
包括两种情况:
1.断开自己与父亲,代价为从根到该节点的最小值。
2.不考虑该点,子树内的所有询问点都断开的代价。

具体看代码吧。

#include "bits/stdc++.h"

using namespace std;
inline int read() {
    int x = 0;
    bool f = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = 0;
    for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
    if (f) return x;
    return 0 - x;
}
#define SZ(x) ((int)x.size())
#define ll long long

const int maxn = 250000 + 10;

struct edge {
    int u, v, w, nxt;
} ed[maxn << 1];
int head[maxn << 1], cnt;
void add_e(int u, int v, int w) {
    ed[++cnt] = edge{u, v, w, head[u]};
    head[u] = cnt;
}
int top[maxn], id[maxn], f[maxn], dep[maxn], sz[maxn], son[maxn], rk[maxn];
ll mn[maxn];

void dfs1(int u, int fa, int d) {
    f[u] = fa, dep[u] = d, sz[u] = 1;
    for (int i = head[u]; i; i = ed[i].nxt) {
        int v = ed[i].v;
        if (v == fa) continue;
        mn[v] = min(mn[u], 1ll * ed[i].w);
        dfs1(v, u, d + 1);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int t) {
    top[u] = t, id[u] = ++cnt, rk[cnt] = u;
    if (!son[u]) return;
    dfs2(son[u], t);
    for (int i = head[u]; i; i = ed[i].nxt) {
        int v = ed[i].v;
        if (v != son[u] && v != f[u]) dfs2(v, v);
    }
}

int LCA(int x, int y) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        x = f[top[x]];
    }
    if (dep[x] > dep[y]) return y;
    return x;
}
bool cmp(int const &a, int const &b) {
    return id[a] < id[b];
}
int n, m, k, a[maxn], s[maxn], st;
vector<int> g[maxn];

void build(int x) {
    if (st == 1) {
        s[++st] = x;
        return;
    }
    int lca = LCA(x, s[st]);
    if (lca == s[st]) return;
    while (st > 1 && id[s[st - 1]] >= id[lca]) {
        g[s[st - 1]].push_back(s[st]), st--;
    }
    if (lca != s[st]) g[lca].push_back(s[st]), s[st] = lca;
    s[++st] = x;
}

ll solve(int u) {
    if (SZ(g[u]) == 0) return mn[u];
    ll sum = 0;
    for (int v:g[u]) {
        sum += solve(v);
    }
    g[u].clear();
    return min(sum, mn[u]);
}

int main() {
    mn[1] = 1ll << 60;
    n = read();
    for (int i = 1, u, v, w; i < n; i++) {
        u = read();
        v = read();
        w = read();
        add_e(u, v, w);
        add_e(v, u, w);
    }
    dfs1(1, 0, 1);
    cnt = 0;
    dfs2(1, 1);
    m = read();
    while (m--) {
        k = read();
        for (int i = 1; i <= k; i++) a[i] = read();
        sort(a + 1, a + k + 1, cmp);
        s[st = 1] = 1;
        for (int i = 1; i <= k; i++) build(a[i]);
        while (st > 0) g[s[st - 1]].push_back(s[st]), st--;

        cout << solve(1) << endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值