虚树#1
基环树那块闲了再写。
本文针对虚树板题作原理解释和介绍写法。
如果不考虑多测那么这是一道裸的树形dp。
令
其中
也就是:要么把
时间复杂度
考虑优化,注意到
也就是说,唯一有用的点是查询的关键点和他们的
用这些点捏一棵新的树,则
这里借用题解的图方便理解。
从原树中提取的新树长这样。
可以发现,答案没有变化。
这个思想就叫做虚树。
现在考虑如何构建虚树。
构建思想和笛卡尔树很像,都使用单调栈维护右链的方式。
首先把关键点按 dfs序 排序,将第一个点入栈。
令
说明节点
- lca 在
到 的路径上。
此时弹出
这个时候只弹栈顶,把
此时
于是可以跑出本题的虚树的建树过程。
for(int i=2;i<=k;i++){
int u=tar[i],anc=lca(u,stac[tt]);//跑出lca
while(1){
if(dep[anc]>=dep[stac[tt-1]]){//第四种情况以外的情况,此时stactop和stactop-1能够界定lca的位置。
if(anc!=stac[tt]){//只要lca不是栈顶那就得踢掉(2,3种情况)
readd(anc,stac[tt]);
if(anc!=stac[tt-1])stac[tt]=anc;//第二种情况。
else --tt;//第三种情况。
}
break;
}
else{
readd(stac[tt-1],stac[tt]);
--tt;
}
}
stac[++tt]=u;//无论如何 u 都入栈。
}
最后右链很可能还是有东西的,把右链逐个连边。
while(--tt)readd(stac[tt],stac[tt+1]);
然后在新图上跑
提供完整代码。
#include<bits/stdc++.h>
#define MAXN 500005
#define int long long
using namespace std;
int n,m,k;
const int inf=1e18;
struct node{
int v,w,nxt;
}edge[MAXN];
int h[MAXN],tmp;
inline void add(int u,int v,int w){
edge[++tmp].v=v;edge[tmp].w=w;
edge[tmp].nxt=h[u];
h[u]=tmp;
}
int lcafa[25][MAXN],dfn[MAXN],tim,dep[MAXN],minv[MAXN];
inline void dfs1(int u){
for(int i=0;lcafa[i][u];i++){
lcafa[i+1][u]=lcafa[i][lcafa[i][u]];
}
dfn[u]=++tim;
for(int i=h[u];i;i=edge[i].nxt){
int v=edge[i].v,w=edge[i].w;
if(!dfn[v]){
dep[v]=dep[u]+1;
minv[v]=min(minv[u],w);
lcafa[0][v]=u;
dfs1(v);
}
}
}
inline int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[lcafa[i][x]]>=dep[y])x=lcafa[i][x];
}
if(x==y)return x;
for(int i=20;i>=0;i--){
if(lcafa[i][x]!=lcafa[i][y]){
x=lcafa[i][x];
y=lcafa[i][y];
}
}
return lcafa[0][x];
}
inline void INIT(){
scanf("%lld",&n);
for(int i=1,u,v,w;i<n;i++){
scanf("%lld%lld%lld",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
minv[1]=inf;
dfs1(1);
}
bool cmp(int x,int y){
return dfn[x]<dfn[y];
}
int tar[MAXN];
bool que[MAXN];
int stac[MAXN],tt;
node nedge[MAXN];
int nh[MAXN],ntmp;
inline void readd(int u,int v){
nedge[++ntmp].v=v;
nedge[ntmp].nxt=nh[u];
nh[u]=ntmp;
}
inline int dfs(int u){
int sum=0,res=inf;
for(int i=nh[u];i;i=nedge[i].nxt){
int v=nedge[i].v;
sum+=dfs(v);
}
if(que[u])res=minv[u];
else res=min(minv[u],sum);
que[u]=0;
nh[u]=0;
return res;
}
inline void Work(){
scanf("%lld",&m);
while(m--){
scanf("%lld",&k);
for(int i=1;i<=k;i++)scanf("%lld",&tar[i]),que[tar[i]]=1;
sort(tar+1,tar+1+k,cmp);
tt=1,stac[tt]=tar[1];
for(int i=2;i<=k;i++){
int u=tar[i],anc=lca(u,stac[tt]);
while(1){
if(dep[anc]>=dep[stac[tt-1]]){
if(anc!=stac[tt]){
readd(anc,stac[tt]);
if(anc!=stac[tt-1])stac[tt]=anc;
else --tt;
}
break;
}
else{
readd(stac[tt-1],stac[tt]);
--tt;
}
}
stac[++tt]=u;
}
while(--tt)readd(stac[tt],stac[tt+1]);
printf("%lld\n",dfs(stac[1]));
ntmp=0;
}
}
signed main(){
INIT();
Work();
return 0;
}
虚树的
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律