P2495 【模板】虚树 / [SDOI2011] 消耗战

题意

给一颗带权树,每次询问给一些节点,需要断掉一些边,断掉这条边的代价为祂的边权,使得给出的节点都不与 \(1\) 联通,问最小代价。

思路

虚树板子题。
对于一次询问 \((m,h_1,h_2,\dots,h_m)\),把 \(h_1,h_2,\dots,h_m\) 按照 \(dfn\) 排序,然后相邻两个求 \(lca\),把 \(1,h_1,h_2,\dots,h_m\) 和求出来的所有 \(lca\) 都当作关键点,建出虚树,跑 \(dp\)
考虑如何建虚树。把所有关键点按照 \(dfn\) 排序,每个点向祂和祂前面一个点的 \(lca\) 连边,边权是原树路径上边权最小值,显然,这样是不会跨过关键点连边的,因为如果跨过了关键点连边,那么被跨过的那个点的 \(dfn\) 是夹在当前点和前一个点的 \(dfn\) 之间的,而 \(dfn\) 是按照升序排序的,矛盾。
那么我们建出了虚树,设 \(f_{i,0/1}\) 表示 \(i\) 的子树内有/没有被切断的最小代价,转移 \(f_{u,1}=\sum_{v\in son(u)}\min(f_{v,1},f_{v,0}+val_{u,v})\ \ f_{u,0}=\sum_{v\in son(u)}\min(f_{v,1},f_{v,0})\),答案即为 \(f_{1,1}\)

代码

/*
Luogu P2495 【模板】虚树 / [SDOI2011] 消耗战
2026-04-09
*/
#include<bits/stdc++.h>
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T&x){
        x=0;char c=getchar();bool f=0;
        while(!isdigit(c)) c=='-'?f=1:0,c=getchar();
        while(isdigit(c)) x=x*10+c-'0',c=getchar();
        f?x=-x:0;
    }
    template<typename T>
    inline void write(T x){
        if(x==0){putchar('0');return ;}
        x<0?x=-x,putchar('-'):0;short st[50],top=0;
        while(x) st[++top]=x%10,x/=10;
        while(top) putchar(st[top--]+'0');
    }
    inline void read(char&c){c=getchar();while(isspace(c)) c=getchar();}
    inline void write(char c){putchar(c);}
    inline void read(string&s){s.clear();char c;read(c);while(!isspace(c)&&~c) s+=c,c=getchar();}
    inline void write(string s){for(int i=0,len=s.size();i<len;i++) putchar(s[i]);}
    template<typename T>inline void write(T*x){while(*x) putchar(*(x++));}
    template<typename T,typename...T2> inline void read(T&x,T2&...y){read(x),read(y...);}
    template<typename T,typename...T2> inline void write(const T x,const T2...y){write(x),putchar(' '),write(y...),sizeof...(y)==1?putchar('\n'):0;}
}using namespace IO;
#define LL long long
const int maxn=250010,inf=1000000000;
const LL infLL=10000000000000000;
int n,dfn[maxn],cnt_dfn,fa[maxn][20],val[maxn][20],deep[maxn],m,h[maxn*2],cnt_h;
LL f[maxn][2];
bool flag[maxn];
vector<pair<int,int>>e[maxn],r[maxn];
void dfs(int u){
    deep[u]=deep[fa[u][0]]+1;
    for(int i=1;i<=18;i++) fa[u][i]=fa[fa[u][i-1]][i-1],val[u][i]=min(val[u][i-1],val[fa[u][i-1]][i-1]);
    dfn[u]=++cnt_dfn;
    for(auto[v,z]:e[u]){
        if(v==fa[u][0]) continue;
        fa[v][0]=u,val[v][0]=z;
        dfs(v);
    }
}
int lca(int u,int v){
    if(deep[u]<deep[v]) swap(u,v);
    for(int i=18;i>=0;i--) if(deep[fa[u][i]]>=deep[v]) u=fa[u][i];
    if(u==v) return u;
    for(int i=18;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
int query(int u,int v){
    int ans=inf;
    for(int i=18;i>=0;i--) if(deep[fa[u][i]]>=deep[v]) ans=min(ans,val[u][i]),u=fa[u][i];
    return ans;
}
bool cmp(int a,int b){return dfn[a]<dfn[b];}
void dp(int u){
    f[u][1]=f[u][0]=0;
    if(flag[u]) f[u][1]=infLL;
    for(auto[v,z]:r[u]){
        dp(v);
        f[u][1]+=min(f[v][1],f[v][0]+z);
        f[u][0]+=min(f[v][1],f[v][0]);
    }
}
signed main(){
    read(n);
    for(int i=1;i<n;i++){
        int u,v,w;read(u,v,w);
        e[u].push_back({v,w}),e[v].push_back({u,w});
    }
    dfs(1);
    read(m);
    for(int i=1;i<=m;i++){
        int k;read(k);
        for(int i=1;i<=k;i++) read(h[i]),flag[h[i]]=1;
        cnt_h=k;
        h[++cnt_h]=1;
        sort(h+1,h+1+cnt_h,cmp);
        for(int i=2;i<=k+1;i++) h[++cnt_h]=lca(h[i],h[i-1]);
        sort(h+1,h+1+cnt_h);cnt_h=unique(h+1,h+1+cnt_h)-h-1;
        sort(h+1,h+1+cnt_h,cmp);
        for(int i=2;i<=cnt_h;i++){
            int fa=lca(h[i],h[i-1]),val=query(h[i],fa);
            if(fa==h[i]) continue;
            r[fa].push_back({h[i],val});
        }
        dp(1);
        for(int i=1;i<=cnt_h;i++) r[h[i]].clear(),flag[h[i]]=0;
        write(f[1][1]);write("\n");
    }
    return 0;
}
posted @ 2026-04-09 19:08  Link-Cut_Trees  阅读(19)  评论(0)    收藏  举报