虚树 virtual-tree

我们发现,如果一棵树中真正需要处理的点很少,而总共点数很多时,可以只处理那些需要的点,而忽略其他点。

因此我们可以根据那些需要的点构建虚树,只保留关键点。

oi-wiki上对虚树的介绍

我们根据一下方式建立虚树:

  • 现将所有需要处理的关键按照欧拉序排序。

  • 用一个栈维护一条从根节点到上一个处理过个点的链(虚树上的链)。

  • 考虑将一个新的点加入虚树:

    • 求出这个点与栈顶元素的 (Lca)

    • 如果 (Lca) 不是栈顶元素:

      • 在栈中只保留栈中的链与现在加入点所在的链的公共部分加上一个上一次处理完的链中元素(通过 (Lca)(dfn) )。

      • 如果 (Lca) 已经在栈中,则弹出那个多余的元素。

      • 如果 (Lca) 还不在栈中,则将 (Lca) 与多余元素连边,并加入 (Lca)

    • 把新的点加入栈中。

    • 处理完后把栈中的链也连边连上。

注意:由于整个图需要用到的边与点很少,所以在每次新建虚树的时候不能全局清空,而是在把一个新的点加入栈中的时候清空这个点连过的边。

建立虚树代码:

inline void build()
{
	 sort(query+1,query+m+1,cmp),tot=tp=0;
	 sta[++tp]=1,hea[1]=0;
	 for(int i=1,l;i<=m;i++)
	 {
	 	 if(query[i]==1) continue;
	 	 l=Lca(query[i],sta[tp]);
	 	 if(l!=sta[tp])
	 	 {
	 	 	 while(dfn[l]<dfn[sta[tp-1]])
			 {
			 	 Lca(sta[tp-1],sta[tp]); // 这里用来处理这一条虚树中的边的权值。
				 add(sta[tp-1],sta[tp],minn);
				 tp--;
			 }
	 	 	 if(sta[tp-1]!=l)
			 {
			 	 hea[l]=0;
				 Lca(l,sta[tp]); // 这里用来处理这一条虚树中的边的权值。
				 add(l,sta[tp],minn);
				 sta[tp]=l;
			 }
	 	 	 else
			 {
			 	 Lca(l,sta[tp]); // 这里用来处理这一条虚树中的边的权值。
				 add(l,sta[tp--],minn);
			 }
		 }
		 hea[query[i]]=0,sta[++tp]=query[i];
	 }
	 for(int i=1;i<tp;i++)
	 {
	 	 Lca(sta[i],sta[i+1]); // 这里用来处理这一条虚树中的边的权值。
		 add(sta[i],sta[i+1],minn);
	 }
}

建立完之后就可以在虚树上处理答案了,这样即使多次询问,复杂度也是和选中关键点个数同阶的。

例题:

世界树 $- exttt{code}$
// Author:A weak man named EricQian
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define Maxn 300005
#define Maxpown 21
#define pb push_back
#define pa pair<int,int>
#define fi first
#define se second
typedef long long ll;
inline int rd()
{
	 int x=0;
     char ch,t=0;
     while(!isdigit(ch = getchar())) t|=ch=='-';
     while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
     return x=t?-x:x;
}
int n,m,q,tot,Time,tp;
struct Dot{ int num,pointnum; }query[Maxn];
int dep[Maxn],siz[Maxn],dfn[Maxn],sta[Maxn],fa[Maxn][Maxpown]; // 处理虚树 
bool exist[Maxn]; // 处理虚树 
int ans[Maxn],belson[Maxn],hea[Maxn],nex[Maxn],ver[Maxn],edg[Maxn]; // 虚树 
// belson[v] : u 是 v 在 虚树上的父亲,belson 是 v 属于 u 的那一个原图上的儿子 
pa Near[Maxn];
vector<int> g[Maxn]; // 原图 
void dfspre(int x)
{
	 dfn[x]=++Time,siz[x]=1;
	 for(int v:g[x])
	 {
	 	 if(v==fa[x][0]) continue;
	 	 fa[v][0]=x,dep[v]=dep[x]+1;
	 	 for(int i=1;i<=20;i++) fa[v][i]=fa[fa[v][i-1]][i-1];
	 	 dfspre(v);
	 	 siz[x]+=siz[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[fa[y][i]]>=dep[x]) y=fa[y][i];
	 if(x==y) return x;
	 for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i])
	 	 x=fa[x][i],y=fa[y][i];
	 return fa[x][0];
}
inline void add(int x,int y,int d){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot,edg[tot]=d; }
bool cmp1(Dot x,Dot y){ return dfn[x.pointnum]<dfn[y.pointnum]; }
bool cmp2(Dot x,Dot y){ return x.num<y.num; }
inline void build()
{
 	 m=rd();
 	 for(int i=1;i<=m;i++) query[i]=(Dot){i,rd()},exist[query[i].pointnum]=true;
 	 sort(query+1,query+m+1,cmp1);
 	 tot=0,sta[tp=1]=1,hea[1]=0;
 	 for(int i=1,l;i<=m;i++)
 	 {
 	 	 if(query[i].pointnum==1) continue;
 	 	 l=Lca(sta[tp],query[i].pointnum);
 	 	 if(l!=sta[tp])
 	 	 {
 	 	 	 while(dfn[l]<dfn[sta[tp-1]])
				 add(sta[tp-1],sta[tp],dep[sta[tp]]-dep[sta[tp-1]]),tp--;
 	 	 	 if(sta[tp-1]!=l)
				 hea[l]=0,add(l,sta[tp],dep[sta[tp]]-dep[l]),sta[tp]=l;
 	 	 	 else add(l,sta[tp],dep[sta[tp]]-dep[l]),tp--;
		 }
		 hea[query[i].pointnum]=0,sta[++tp]=query[i].pointnum;
	 }
	 for(int i=1;i<tp;i++) add(sta[i],sta[i+1],dep[sta[i+1]]-dep[sta[i]]);
}
void dfs1(int x) // 处理最近的特殊点-1 (+init)
{
	 if(exist[x]) Near[x]=pa(0,x);
	 else Near[x]=pa(inf,inf);
	 ans[x]=0;
	 for(int i=hea[x],tmp,Now;i;i=nex[i])
	 {
	 	 dfs1(ver[i]);
	 	 Now=Near[x].fi,tmp=Near[ver[i]].fi+edg[i];
	 	 if((tmp<Now) || (tmp==Now && Near[ver[i]].se<Near[x].se))
		  	 Near[x].fi=tmp,Near[x].se=Near[ver[i]].se;
	 }
}
void dfs2(int x) // 处理最近的特殊点-2
{
	 for(int i=hea[x],Now,tmp;i;i=nex[i])
	 {
	 	 Now=Near[x].fi+edg[i],tmp=Near[ver[i]].fi;
	 	 if((Now<tmp) || (Now==tmp && Near[x].se<Near[ver[i]].se))
		  	 Near[ver[i]].fi=Now,Near[ver[i]].se=Near[x].se;
	 	 dfs2(ver[i]);
	 }
}
void dfs3(int x) // 把在虚树外的点计算(通过倍增到父亲的下面) 
{
	 int All=siz[x];
	 for(int i=hea[x],tmp;i;i=nex[i])
	 {
	 	 tmp=ver[i];
	 	 for(int j=20;j>=0;j--) if(dep[fa[tmp][j]]>dep[x]) tmp=fa[tmp][j];
	 	 All-=siz[tmp],belson[ver[i]]=tmp;
	 	 dfs3(ver[i]);
	 }
	 ans[Near[x].se]+=All;
}
void dfs4(int x) // 计算中间的点 
{
	 for(int i=hea[x];i;i=nex[i])
	 {
	 	 if(Near[x].se==Near[ver[i]].se)
	 	 	 ans[Near[x].se]+=siz[belson[ver[i]]]-siz[ver[i]];
	 	 else
	 	 {
	 	 	 int downdep=dep[Near[ver[i]].se]+dep[x]-Near[x].fi;
	 	 	 if(downdep & 1) downdep=downdep/2+1;
			 else downdep=(Near[x].se<Near[ver[i]].se)?(downdep/2+1):(downdep/2);
			 int tmp=ver[i];
			 for(int j=20;j>=0;j--) if(dep[fa[tmp][j]]>=downdep) tmp=fa[tmp][j];
			 ans[Near[x].se]+=siz[belson[ver[i]]]-siz[tmp];
			 ans[Near[ver[i]].se]+=siz[tmp]-siz[ver[i]];
		 }
		 dfs4(ver[i]);
	 }
}
int main()
{
	 //ios::sync_with_stdio(false); cin.tie(0);
     //freopen(".in","r",stdin);
     //freopen(".out","w",stdout);
	 n=rd();
	 for(int i=1,x,y;i<n;i++) x=rd(),y=rd(),g[x].pb(y),g[y].pb(x);
	 dep[1]=1,dfspre(1),q=rd();
	 for(int i=1;i<=q;i++)
	 {
	 	 build();
	 	 dfs1(1),dfs2(1); // 处理好最近的点 
	 	 dfs3(1),dfs4(1); // 计算答案 
	 	 sort(query+1,query+m+1,cmp2);
	 	 for(int j=1;j<=m;j++) printf("%d%c",ans[query[j].pointnum],(j==m)?'
':' ');
	 	 for(int j=1;j<=m;j++) exist[query[j].pointnum]=false;
	 }
     //fclose(stdin);
     //fclose(stdout);
     return 0;
}
原文地址:https://www.cnblogs.com/EricQian/p/15359399.html