前言
前几天做比赛遇到一道题,考场想到了虚树,但是真正实现起来发现不太会打,所以回去复习了一下虚树。
然而那道题正解并不是虚树。
正文
以【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;
}