虚树

虚树

在处理一些树上的询问时,总询问点数为 $O(n)$, 但询问次数可能很多, 这时就需要虚树.

虚树的思想是尽可能用少的节点将询问点数串联起来.    

对于询问点,虚树建立后只包含询问点之间的 $\mathrm{lca}$ 以及询问点本身.   

虚树的构建 

先对所有询问点按照 $\mathrm{dfs}$ 序排序.   

维护一个栈,栈里的元素代表从上到下的一条链且栈顶为链底.(为了方便一般将根节点加入)

考虑现在加入询问点 $\mathrm{x}$.   

分几种情况讨论:

1. 栈中元素个数小于等于 $1$ 则直接加入.

2. $\mathrm{LCA(x,s[top])=s[top]}$, 即 $\mathrm{x}$ 位于 $s[top]$ 的子树中,同样直接加入.

3. $\mathrm{LCA(x,s[top])!=s[top]}$, 即 $\mathrm{s[top]}$ 与 $\mathrm{x}$ 位于 $\mathrm{lca}$ 的两个子树中.    

对于第 $3$ 种情况, 就不断弹栈, 并让栈顶元素变为 $\mathrm{lca}$, 再连 $\mathrm{lca}$ 与 $\mathrm{x}$ 的边.  

注意:边弹栈边加入虚树边.   

void ins(int x) {
    if(top<=1) {
        sta[++top]=x;  
        return ; 
    }
    int lca=get_lca(sta[top], x);  
    if(lca == sta[top]) {
        // x 在 sta[top] 子树中.  
        sta[++top] = x;
    }
    else {
        while(top>1&&dfn[sta[top-1]]>=dfn[lca]) G[sta[top-1]].pb(sta[top]),--top;  
        // top, lca, top-1  
        if(lca!=sta[top]) G[lca].pb(sta[top]),sta[top]=lca;  
        sta[++top]=x;   
    }
}

  

例题

洛谷P2495 [SDOI2011]消耗战 

虚树裸题, 题目数据范围是假的, 边权要开 long long.   

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm> 
#define N 250009 
#define ll long long 
#define pb push_back 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std;   
const ll inf=10000000000000ll;  
int n ;  
ll mn[N],val[N<<1];  
int sta[N],top;  
vector<int>G[N]; 
int hd[N],to[N<<1],nex[N<<1]; 
int dep[N],fa[20][N],a[N],dfn[N],mk[N],edges,scc; 
void add(int u,int v,ll c) {
    nex[++edges]=hd[u]; 
    hd[u]=edges; 
    to[edges]=v; 
    val[edges]=c; 
}
void dfs(int x, int ff) {
    fa[0][x]=ff; 
    dfn[x]=++scc;  
    for(int i=1;i<=19;++i) fa[i][x]=fa[i-1][fa[i-1][x]];   
    for(int i=hd[x];i;i=nex[i]) {
        int v=to[i]; 
        if(v==ff) continue;  
        dep[v]=dep[x]+1;  
        mn[v]=min(mn[x], 1ll*val[i]);  
        dfs(v, x);   
    }
}
int get_lca(int x, int y) {
    if(dep[x] > dep[y]) swap(x, y);  
    if(dep[x] != dep[y]) {
        for(int i=19;i>=0;--i) 
            if(dep[fa[i][y]] >= dep[x]) y=fa[i][y];   
    }
    if(x==y) return x;  
    for(int i=19;i>=0;--i) 
        if(fa[i][x]!=fa[i][y]) x=fa[i][x],y=fa[i][y];  
    return fa[0][x];   
}   
bool cmp(int i,int j) { return dfn[i] < dfn[j]; }     
void ins(int x) {
    if(top<=1) {
        sta[++top]=x;  
        return ; 
    }
    int lca=get_lca(sta[top], x);  
    if(lca == sta[top]) {
        // x 在 sta[top] 子树中.  
        sta[++top] = x;
    }
    else {
        while(top>1&&dfn[sta[top-1]]>=dfn[lca]) G[sta[top-1]].pb(sta[top]),--top;  
        // top, lca, top-1  
        if(lca!=sta[top]) G[lca].pb(sta[top]),sta[top]=lca;  
        sta[++top]=x;   
    }
}
ll calc(ll x) {
    ll re=mn[x],sum=0; 
    for(int i=0;i<G[x].size();++i) 
        sum+=calc(G[x][i]);   
    if(mk[x]) re=mn[x]; 
    else re=min(sum, 1ll*mn[x]);        
    return re;     
}
void clr(int x) {
    mk[x]=0;   
    for(int i=0;i<G[x].size();++i) clr(G[x][i]); 
    G[x].clear();   
}
void solve() {
    int k; 
    scanf("%d",&k);  
    for(int i=1;i<=k;++i) scanf("%d",&a[i]);  
    sort(a+1, a+1+k, cmp);   
    // 去重.  
    k=unique(a+1, a+1+k)-a-1;   
    sta[top=1]=1;                                      
    for(int i=1;i<=k;++i) mk[a[i]]=1,ins(a[i]); 
    while(top>1) G[sta[top-1]].pb(sta[top]),--top;  
    printf("%lld\n",calc(1));   
    clr(1),top=0;   
}
int main() { 
    // setIO("input");  
    scanf("%d",&n);  
    for(int i=1;i<n;++i) {
        int x,y; 
        ll z; 
        scanf("%d%d%lld",&x,&y,&z);  
        add(x,y,z); 
        add(y,x,z);  
    }
    mn[1]=inf;    
    dfs(1, 0); 
    int m; 
    scanf("%d",&m);  
    for(int i=1;i<=m;++i) solve(); 
    return 0; 
}

  

posted @ 2021-09-11 09:01  guangheli  阅读(80)  评论(0)    收藏  举报