虚树学习笔记

前言

前几天做比赛遇到一道题,考场想到了虚树,但是真正实现起来发现不太会打,所以回去复习了一下虚树。

然而那道题正解并不是虚树。

正文

以【SDOI2011】消耗战 为例。

这道题是很经典的虚树模板,暴力做法很简单,树形dp即可。

但是容易发现,有非常多的点实际上是不需要的,这些都不是特殊点,同时对答案没有影响。

考虑一种建树方式,只包括必经点,在这颗树上进行dp。也就是虚树。

容易发现,只有这些特殊点以及这些点之间的(lca)是必经点,这些必经点的总数是非常有限的,考虑如何找出这些必经点。

将所有点按(dfs)序排序,然后将两两间的lca加入点集,用哈希维护,这是一个非常显然而简单的做法。

考虑更好打而且常数更小更快的做法。

维护一个栈,栈中的点表示一条从根出发的路径。按(dfs)序排序之后,一次将每个点入栈,如果发现这个点可以接在链上就直接插入,如果不行要考虑弹栈。也就是将这条链以外的点弹出。

每次判断当前点与栈顶点的(lca)与栈顶的上一个点的 (dfs)序的大小。因为这两个点都在栈顶点到根的路径上,所以如果比上一个点的(dfs)序小,说明上一个点不在这条新的链上,可以弹出上一个点,并将其加入点集;如果比上一个点大,说明上一个点在路径上,所以将(lca)和当前点入栈并加入点集即可。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500010
#define K 20
#define ll long long
using namespace std;
int n,Q,x,y,w,m,num,i,j,k[N],dfn[N],q[N],dep[N],bz[N],tot,top,stk[N],lca,qv[N],totv,pl,fa[N][K],mn[N][K];
ll dp[N];
struct edge{
	int to,next,len;
}e[N],ev[N];
int read(){
	int x=0;
	char ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x;
}
void insert_edge(int x,int y,int w){
	tot++;
	e[tot].to=y;
	e[tot].next=q[x];
	q[x]=tot;
	e[tot].len=w;
}
void insertv_edge(int x,int y,int w){
	totv++;
	ev[totv].to=y;
	ev[totv].next=qv[x];
	qv[x]=totv;
	ev[totv].len=w;
}
void dfs(int x,int father){
	int i,y;
	fa[x][0]=father;
	for (i=1;i<K;i++)
		fa[x][i]=fa[fa[x][i-1]][i-1],mn[x][i]=min(mn[x][i-1],mn[fa[x][i-1]][i-1]);
	dep[x]=dep[father]+1;
	dfn[x]=++num;
	for (i=q[x];i;i=e[i].next){
		y=e[i].to;
		if (y!=father)
			mn[y][0]=e[i].len,dfs(y,x);
	}
}
int cmp(int x,int y){
	return dfn[x]<dfn[y];
}
int getlca(int x,int y){
	int i;
	if (dep[x]<dep[y]) swap(x,y);
	for (i=K-1;i>=0;i--) if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];
	if (x==y) return x;
	for (i=K-1;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
int getmin(int x,int y){
	int i,res=1e9;
	if (x==y) return 0;
	for (i=K-1;i>=0;i--) if (fa[x][i]!=y) res=min(mn[x][i],res),x=fa[x][i];
	return res;
}
void dfs_ans(int x,int father,int ver){
	int i,y;
	dp[x]=0;
	for (i=qv[x];i;i=ev[i].next){
		y=ev[i].to;
		dfs_ans(y,x,ver);
		if (bz[y]==ver) dp[x]+=ev[i].len;
		else dp[x]+=min((ll)ev[i].len,dp[y]);
	}
}
int main(){
	freopen("virtual_tree.in","r",stdin);
	freopen("virtual_tree.out","w",stdout);
	n=read();
	for (i=1;i<n;i++){
		x=read();y=read();w=read();
		insert_edge(x,y,w);
		insert_edge(y,x,w);
	}
	tot=num=0;
	memset(mn,127,sizeof(mn));
	dfs(1,0);
	Q=read();
	while (Q--){
		num=read();
		for (i=1;i<=num;i++) k[i]=read();
		top=1;stk[top]=1;
		sort(k+1,k+num+1,cmp);
		pl=1;
		qv[1]=0;
		totv=0;
		for (i=1;i<=num;i++){
			lca=getlca(k[i],stk[top]);
			bz[k[i]]=Q;
			if (lca==stk[top]) stk[++top]=k[i],qv[k[i]]=0;
			else{
				while (dfn[lca]<dfn[stk[top-1]]&&top>1){
					insertv_edge(stk[top-1],stk[top],getmin(stk[top],stk[top-1]));
					top--;
				}
				if (dfn[lca]>dfn[stk[top-1]]){
					qv[lca]=0;
					insertv_edge(lca,stk[top],getmin(stk[top],lca));
					top--;
					stk[++top]=lca;
				}
				else{
					insertv_edge(lca,stk[top],getmin(stk[top],lca));
					top--;
				}
				stk[++top]=k[i];
				qv[k[i]]=0;
			}
		}
		while (top>1){
			insertv_edge(stk[top-1],stk[top],getmin(stk[top],stk[top-1]));
			top--;
		}
		dfs_ans(1,0,Q);
		printf("%lld
",dp[1]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Mohogany/p/14044796.html