虚树
虚树
所谓虚树,就是对于一棵指定的树
虚树能解决什么问题?优化树形 DP。比如给定多组询问,每组询问给出树上的一些关键点,最终的答案只和这些关键点有关。那么此时我们如果每次都
建树
有两种建树方法,OI Wiki 上有讲。一般来说我们使用单调栈建树。
先预处理整棵树的 DFS 序和 LCA,然后对于每组询问的关键点构造虚树。
- 先对所有关键点
按照 DFS 序排序。 - 建一个栈
,满足 , , 为 的后代。每当 被弹出栈时,建立 的边。当我们要给栈里加入一个新的节点 时,设 ,分类讨论: ,也就是 是 子树内的节点,此时直接将 入栈; ,也就是 不是 子树内的节点,此时我们把栈弹出,每弹出一个节点就加一条边,直到变成第一种情况。
- 最后,把尚未弹出栈的节点依次弹出,并连边。
最后的实现是这样的:
int st[MAXN],tp;
void ins(int x){
if(!tp) return st[++tp]=x,void();
int l=lca(st[tp],x);
while(tp>1&&dep[l]<dep[st[tp-1]]){
addedge(st[tp-1],st[tp]);
--tp;
}
if(dep[l]<dep[st[tp]]) addedge(l,st[tp]),--tp;
if(!tp||st[tp]!=l) st[++tp]=l;
st[++tp]=x;
}
int main(){
...
for(int i=1,k;i<=m;++i){
k=read();
for(int j=1;j<=k;++j) a[j]=read();
sort(a+1,a+k+1,[](int x,int y){return dfn[x]<dfn[y];});
if(a[j]!=1) st[++tp]=1;
for(int j=1;j<=k;++j) ins(a[j]);
if(tp) while(--tp) addedge(st[tp],st[tp+1]);
}
}
其实,这个建树的板子还是直接背最方便。
清空
虚树的清空是很需要注意的,为了保持复杂度正确,我们不能 memset
之类,只能每次扫关键点地清空,或者在 DFS 函数的最后清空。下面的例题有这一点。
P2495 [SDOI2011] 消耗战
题意:给定一棵
个点的树和 个询问,边有边权。每个询问给定一些树上的关键点,求出使 节点不能到达任何关键点所需要断开的最少边权和。 , 。
看到这种
首先容易得到的是一个
- 若
是关键点, ; - 若
不是关键点, ;
但是我们发现因为
所以把虚树建出来,然后在虚树上进行 DP 就可以了。DP 实际上不是本题的难点。
// 前面的快读、树剖不再展示
int st[MAXN],tp;
bool vis[MAXN];
void ins(int x){
if(!tp) return st[++tp]=x,void();
int l=lca(st[tp],x);
while(tp>1&&dep[l]<dep[st[tp-1]]){
addedge(st[tp-1],st[tp]);
--tp;
}
if(dep[l]<dep[st[tp]]) addedge(l,st[tp]),--tp;
if(!tp||st[tp]!=l) st[++tp]=l;
st[++tp]=x;
}
ll dfs3(int u){
ll sum=0;
for(int i=head[u];i;i=e[i].to) sum+=dfs3(e[i].v);
ll res=vis[u]?dd[u]:min(sum,dd[u]);
vis[u]=0;
head[u]=0;
return res;
}
int main(){
n=read();
for(int i=1,u,v,w;i<n;++i){
u=read(),v=read(),w=read();
addedge(u,v,w),addedge(v,u,w);
}
dd[1]=2e18;
dfs(1,0);
dfs2(1,1);
memset(head,0,sizeof(int)*(n+5));
tot=0;
m=read();
for(int i=1,k;i<=m;++i){
k=read();
for(int j=1;j<=k;++j) a[j]=read(),vis[a[j]]=1;
sort(a+1,a+k+1,[](int x,int y){return dfn[x]<dfn[y];});
st[++tp]=1;
for(int j=1;j<=k;++j) ins(a[j]);
if(tp) while(--tp) addedge(st[tp],st[tp+1]);
write(dfs3(1));
tp=tot=0;
}
return fw,0;
}
注意到原树在求出 DFS 序和 LCA 之后就没有用了,所以虚树可以直接使用原树的链式前向星建树。注意我们在 DFS 函数的末尾就把 vis
和 head
清空了,这样才能保证复杂度的正确。
虚树的题本来想再贴几道,结果发现要么可以不拿虚树做,要么难点不在虚树。其实大多数虚树的题难点还是在 DP 上,虚树就是个板子,建完了之后调用就可以。
所以就不贴了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】