• 虚树 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;
    }
    
  • 相关阅读:
    http://www.cnblogs.com/zhengyun_ustc/p/55solution2.html
    http://www.cnblogs.com/carekee/articles/1854674.html
    http://www.blogjava.net/xylz/archive/2010/07/08/325587.html
    http://honda418.iteye.com/blog/337052/
    解决myeclipse每次启动注册码过期输入注册码
    JavaScript只能为汉字
    解决 不允许一个用户使用一个以上用户名与一个服务器或共享资源的多重连接
    websphere变成英文了怎么变回中文
    【技术贴】解决MySql连接不上 ip远程连接Host is not allowed to conn
    【技术贴】解决bug mantisbt APPLICATION ERROR #1502 没有找到类别
  • 原文地址:https://www.cnblogs.com/EricQian/p/15359399.html
Copyright © 2020-2023  润新知