虚树
虚树
在处理一些树上的询问时,总询问点数为 $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; }