C24 虚树 P2495 [SDOI2011] 消耗战
视频链接:247 虚树 [SDOI2011] 消耗战_哔哩哔哩_bilibili

// 树上倍增+虚树+树形DP 2.0s #include <iostream> #include <cstring> #include <algorithm> #define LL long long using namespace std; const int N=250005; int h[N],tot; struct edge{ int x,y,w,ne; } e[N<<1]; void add(int x,int y,int w){ //连边 e[++tot]={x,y,w,h[x]}; h[x]=tot; } int dep[N],fa[N][20],siz[N]; int dfn[N],cnt; //dfs序 int s[N],top; //栈 int n,m,k,a[N]; LL mn[N]; //mn[x]:从根到x的最小边权 void dfs(int x, int f){ //树上倍增 dfn[x]=++cnt; dep[x]=dep[f]+1; fa[x][0]=f; siz[x]=1; for(int i=1; i<=19; i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for(int i=h[x]; i; i=e[i].ne){ int y=e[i].y; if(y==f) continue; mn[y]=min(mn[x],(LL)e[i].w); dfs(y,x); siz[x]+=siz[y]; } } int lca(int x, int y){ //求lca if(dep[x]<dep[y])swap(x, y); for(int i=19; ~i; i--) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i]; if(x==y) return y; for(int i=19; ~i; i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i]; return fa[x][0]; } int cmp(int a,int b){ return dfn[a]<dfn[b]; } void build(){ //建虚树 sort(a+1,a+k+1,cmp); tot=0; //清空 s[top=1]=1; //根节点入栈 s[++top]=a[1]; for(int i=2; i<=k; i++){ //枚举查询点 int l=lca(a[i],s[top]); if(l==s[top]) continue; //剪枝 // 对当前链连边,top出栈 while(top>1 && dep[s[top-1]]>=dep[l]) add(s[top-1],s[top],0), top--; // 对lca和top连边,top出栈,lca入栈 if(l!=s[top]) add(l,s[top],0), s[top]=l; // 查询点入栈 s[++top]=a[i]; } while(top) //对最后一条链连边,top出栈 add(s[top-1],s[top],0),top--; } LL DP(int x){ //树形DP if(h[x]==0) return mn[x]; //叶子 LL sum=0; for(int i=h[x]; i; i=e[i].ne) sum+=DP(e[i].y); h[x]=0; //清空 return min(sum,mn[x]); } int main(){ scanf("%d",&n); for(int i=1; i<=n-1; i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } mn[1]=2e18; dfs(1,0); memset(h+1,0,n<<2); //清空 scanf("%d",&m); while(m--){ scanf("%d",&k); for(int i=1; i<=k; i++) scanf("%d",&a[i]); build(); printf("%lld\n",DP(1)); } }

// 树上倍增+虚树+树形DP 2.1s #include <iostream> #include <cstring> #include <algorithm> #define LL long long using namespace std; const int N=250005; int h[N],tot; struct edge{ int x,y,w,ne; } e[N<<1]; void add(int x,int y,int w){ //连边 e[++tot]={x,y,w,h[x]}; h[x]=tot; } int dep[N],fa[N][20],siz[N]; int dfn[N],cnt; //dfs序 int s[N],top; //栈 int n,m,k,a[N],vis[N]; LL mn[N]; //mn[x]:从根到x的最小边权 void dfs(int x, int f){ //树上倍增 dfn[x]=++cnt; dep[x]=dep[f]+1; fa[x][0]=f; siz[x]=1; for(int i=1; i<=19; i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for(int i=h[x]; i; i=e[i].ne){ int y=e[i].y; if(y==f) continue; mn[y]=min(mn[x],(LL)e[i].w); dfs(y,x); siz[x]+=siz[y]; } } int lca(int x, int y){ //求lca if(dep[x]<dep[y])swap(x, y); for(int i=19; ~i; i--) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i]; if(x==y) return y; for(int i=19; ~i; i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i]; return fa[x][0]; } int cmp(int a,int b){ return dfn[a]<dfn[b]; } void build(){ //建虚树 sort(a+1,a+k+1,cmp); tot=0; //清空 s[top=1]=1; //根节点入栈 s[++top]=a[1]; for(int i=2; i<=k; i++){ //枚举查询点 int l=lca(s[top],a[i]); // 对当前链连边,top出栈 while(top>1 && dep[s[top-1]]>=dep[l]) add(s[top-1],s[top],0), top--; // 对lca和top连边,top出栈,lca入栈 if(l!=s[top]) add(l,s[top],0), s[top]=l; // 查询点入栈 s[++top]=a[i]; } while(top) //对最后一条链连边,top出栈 add(s[top-1],s[top],0),top--; } LL DP(int x){ //树形DP if(vis[x]){ //x是查询点 for(int i=h[x]; i; i=e[i].ne) DP(e[i].y), vis[e[i].y]=0; h[x]=0; //清空 return mn[x]; } else{ //x不是查询点 LL sum=0; for(int i=h[x]; i; i=e[i].ne) sum+=DP(e[i].y), vis[e[i].y]=0; h[x]=0; //清空 return min(sum,mn[x]); } } int main(){ scanf("%d",&n); for(int i=1; i<=n-1; i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } mn[1]=2e18; dfs(1,0); memset(h+1,0,n<<2); //清空 scanf("%d",&m); while(m--){ scanf("%d",&k); for(int i=1; i<=k; i++) scanf("%d",&a[i]),vis[a[i]]=1; build(); printf("%lld\n",DP(1)); } }

// 树链剖分+虚树+树形DP 1.5s #include <iostream> #include <cstring> #include <algorithm> #include <vector> #define LL long long using namespace std; const int N=250005; int h[N],tot; struct edge{ int x,y,w,ne; } e[N<<1]; void add(int x,int y,int w){ //连边 e[++tot]={x,y,w,h[x]}; h[x]=tot; } int fa[N],dep[N],siz[N],son[N],tp[N];//重链 int dfn[N],cnt; //dfs序 int s[N],top; //栈 int n,m,k,a[N]; LL mn[N]; //mn[x]:从根到x的最小边权 void dfs1(int x, int f){ //树链剖分 dfn[x]=++cnt; dep[x]=dep[f]+1; siz[x]=1; fa[x]=f; for(int i=h[x]; i; i=e[i].ne){ int y=e[i].y; if(y==f) continue; mn[y]=min(mn[x],(LL)e[i].w); dfs1(y,x); siz[x]+=siz[y]; if(siz[son[x]]<siz[y]) son[x]=y; } } void dfs2(int x, int t){ //树链剖分 tp[x]=t; //链头 if(!son[x]) return; dfs2(son[x],t); //搜重儿子 for(int i=h[x]; i; i=e[i].ne){ int y=e[i].y; if(!tp[y]) dfs2(y,y); //搜轻儿子 } } int lca(int x,int y){ //求lca while(tp[x]!=tp[y]){ if(dep[tp[x]]<dep[tp[y]]) swap(x,y); x=fa[tp[x]]; } return dep[x]<dep[y]?x:y; } int cmp(int a,int b){ return dfn[a]<dfn[b]; } void build(){ //建虚树 sort(a+1,a+k+1,cmp); tot=0; //清空 s[top=1]=1; //根节点入栈 s[++top]=a[1]; for(int i=2; i<=k; i++){ //枚举查询点 int l=lca(a[i],s[top]); if(l==s[top]) continue; //剪枝 // 对当前链连边,top出栈 while(top>1 && dep[s[top-1]]>=dep[l]) add(s[top-1],s[top],0), top--; // 对lca和top连边,top出栈,lca入栈 if(l!=s[top]) add(l,s[top],0), s[top]=l; // 查询点入栈 s[++top]=a[i]; } while(top) //对最后一条链连边,top出栈 add(s[top-1],s[top],0),top--; } LL DP(int x){ //树形DP if(h[x]==0) return mn[x]; //叶子 LL sum=0; for(int i=h[x]; i; i=e[i].ne) sum+=DP(e[i].y); h[x]=0; //清空 return min(sum,mn[x]); } int main(){ scanf("%d",&n); for(int i=1; i<=n-1; i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } mn[1]=2e18; dfs1(1,0); dfs2(1,1); memset(h+1,0,n<<2); //清空 scanf("%d",&m); while(m--){ scanf("%d",&k); for(int i=1; i<=k; i++) scanf("%d",&a[i]); build(); printf("%lld\n",DP(1)); } }
练习:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!