给定树上一些点集,处理和该点集有关的询问,通常这些询问需要用树形\(DP\)解决
若发现所有询问的点集总大小\(\sum k\leqslant 10^5\),那么就可以考虑用虚树来解决
若原树为

若询问点为\(1\ 2\ 3\),则虚树为

若询问点为\(1\ 3\ 7\ 8\),则虚树为

构造出的虚树为询问点和询问点的\(LCA\),其他不重要的点和边都进行了类似于路径压缩的操作
虚树的构建为增量算法,先将每个询问点按\(dfs\)序排序,用栈来实现,栈内为从根到栈顶元素这条链,这条链同时也是虚树上的链
实现时,先将根节点入栈
当要向虚树内插入节点\(x\)时,分若干情况讨论
① 若栈内只有根节点(\(top=1\)),则直接将\(x\)入栈
② 若\(lca(x,st[top])=st[top]\),说明\(x\)和\(st[top]\)在同一条链上,则
③ 若\(lca(x,st[top])\ne st[top]\),则说明\(x\)和\(st[top]\)分别在两棵不同的子树中,这时\(st[top]\)所在的子树已经在虚树上构建完。那么开始退栈,直到将\(st[top]\)所在的子树全部退出栈,同时连虚树上的边,若操作完后\(lca\)不在栈中,再将其入栈以及连边,最后将\(x\)入栈
最后还需将栈清空,将栈内剩余的点进行连边,构建完整个虚树
\(code:\)
bool cmp(const int &a,const int &b) { return dfn[a]<dfn[b]; } void insert(int x) { if(x==1) return; if(top==1) { st[++top]=x; return; } int anc=lca(x,st[top]); if(anc==st[top]) { st[++top]=x; return; } while(top>1&&dfn[anc]<=dfn[st[top-1]]) add(st[top-1],st[top]),top--; if(anc!=st[top]) add(anc,st[top]),st[top]=anc; st[++top]=x; } ...... sort(query+1,query+k+1,cmp); st[top=1]=1; for(int i=1;i<=k;++i) insert(query[i]); while(top) add(st[top-1],st[top]),top--;
注意在树形\(DP\)后要清空\(head\),若每次建虚树时\(memset\),那么就无法保证时间复杂度
void dp(int x) { ...... head[x]=0; }
来源:https://www.cnblogs.com/lhm-/p/12229631.html