虚树
在树上 DP 时,有时会有大量的无用节点,遍历求解效率低,可以通过建立 虚树——一种只包含有用节点的树,来优化求解过程。
虚树的建立
朴素算法
- 将关键点按 DFS 序排序;
- 遍历一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重;
- 然后根据原树中的祖先后代关系建树。
单调栈做法
先将树根 root 加入单调栈。然后,对于每一个关键节点(已排序),计算它与栈顶的 LCA,分类讨论:
- 如果 LCA 就是栈顶(即这个关键节点与栈顶在一条链上),则删除这个关键节点的所有边,将它压入栈,退出。
- 将栈顶和次栈顶连边,将栈顶弹出,如此重复多次,直到次栈顶为这个关键节点的祖先。
- 若 LCA 为当前栈顶的祖先,将 LCA 与当前栈顶连边并弹出栈顶。
- 否则删除 LCA 的所有边,在 LCA 与栈顶加边,在将栈顶改为 LCA。
- 删除这个关键节点的所有边,将它压入栈。
遍历完每一个关键节点后,别忘了处理栈中的点,将它们相邻两点加边。
//origin 表示原树,virt 表示虚树
virt::total=0;
k=read();
for(i=1;i<=k;i++)query[h[i]=read()]=true;
std::sort(h+1,h+k+1,cmp);
stack[top=1]=h[1];
for(i=2;i<=k;i++){
int _lca=origin::lca(h[i],stack[top]);
if(_lca!=stack[top]){
while(origin::dfn[_lca]<origin::dfn[stack[top-1]])virt::add(stack[top-1],stack[top]),top--;
if(origin::dfn[_lca]>origin::dfn[stack[top-1]])virt::head[_lca]=0,virt::add(_lca,stack[top]),stack[top]=_lca;
else virt::add(_lca,stack[top--]);
}
virt::head[h[i]]=0,stack[++top]=h[i];
}
for(i=1;i<top;i++)virt::add(stack[i],stack[i+1]);
例题
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 消耗战 | SDOI2011R2D2T3 | 提交记录 备份 | |
| 虚树 / 长链剖分 | |||
| 大工程 | HEOI2014D1T3 | ||
| Kingdom and its Cities | CF613D | ||
| 世界树 | HNOI2014D1T1 |