zoukankan      html  css  js  c++  java
  • 【BZOJ4539】[HNOI2016] 树(线段树合并+倍增LCA)

    点此看题面

    大致题意: 给定一棵模板树,要求建立一棵大树,初始与模板树一样。每次操作将模板树中以(x)为根的子树复制为大树中(y)的子节点,将其重新编号并保持相对大小顺序不变。每次询问求两点间的距离。

    建树

    首先我们考虑如何建出这棵大树,由于最多有(10^{10})个点,显然不可能暴力去建。

    其实不难想到,可以把每棵复制到大树中的子树压缩为一个点,然后只需记录下(Rt_i,G_i)两个信息分别表示它的根节点为(Rt_i)以及它接在大树中对应到模板树编号为(G_i)的点之下

    至于如何求出(G_i),我们只要预处理出模板树中每个点的子树大小,那么就能求出每棵复制树对应的节点编号区间,然后只要用(upper\_bound-1)就能求出接在哪棵复制树下。

    求出复制树编号后,我们还需要求出该子树中编号排名为(k)的点的编号,这可以用线段树合并维护子树信息。

    询问

    询问其实也很简单,就是细节比较多,大致分三类讨论:

    • 两点在同一棵复制树中:直接询问两点距离即可。
    • 两点所在的复制树压成的点在大树上为祖先关系:从儿子倍增向上跳,最后加上儿子接入的点和祖先询问的点之间的距离。
    • 两点所在的复制树压成的点在大树上不存在祖先关系:向上跳到(LCA),最后加上两者接入点之间的距离。

    具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define LN 20
    #define LL long long
    using namespace std;
    int n,m;
    class OriginalTree//原树
    {
    	private:
    		int ee,lnk[N+5],sz[N+5],d[N+5],f[N+5][LN+5];struct edge {int to,nxt;}e[N<<1];
    		class SegmentTree//线段树合并
    		{
    			private:
    				#define LT l,mid,O[rt].S[0]
    				#define RT mid+1,r,O[rt].S[1]
    				int Nt;struct node {int V,S[2];}O[N*LN<<1];
    			public:
    				I void Ins(CI x,CI l,CI r,int& rt)
    				{
    					if(!rt&&(rt=++Nt),++O[rt].V,l==r) return;
    					int mid=l+r>>1;x<=mid?Ins(x,LT):Ins(x,RT);
    				}
    				I void Merge(int& x,CI y)
    				{
    					if(!x||!y) return (void)(x|=y);O[++Nt]=O[x],O[x=Nt].V+=O[y].V,
    					Merge(O[x].S[0],O[y].S[0]),Merge(O[x].S[1],O[y].S[1]);
    				}
    				I int Qry(CI k,CI l,CI r,CI rt)
    				{
    					if(l==r) return l;int mid=l+r>>1;
    					return O[O[rt].S[0]].V>=k?Qry(k,LT):Qry(k-O[O[rt].S[0]].V,RT);
    				}
    		}S;int Rt[N+5];
    		I int LCA(RI x,RI y)//倍增LCA
    		{
    			RI i;d[x]<d[y]&&(x^=y^=x^=y);
    			for(i=0;d[x]^d[y];++i) (d[x]^d[y])>>i&1&&(x=f[x][i]);if(x==y) return x;
    			for(i=LN;~i;--i) f[x][i]^f[y][i]&&(x=f[x][i],y=f[y][i]);return f[x][0];
    		}
    	public:
    		I int operator [] (CI x) {return sz[x];}I int operator () (CI x) {return d[x];}
    		I void Add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
    		I void dfs(CI x=1)//预处理
    		{
    			RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];
    			for(S.Ins(x,1,n,Rt[x]),sz[x]=1,i=lnk[x];i;i=e[i].nxt) e[i].to^f[x][0]&&
    				(d[e[i].to]=d[f[e[i].to][0]=x]+1,dfs(e[i].to),S.Merge(Rt[x],Rt[e[i].to]),sz[x]+=sz[e[i].to]);
    		}
    		I int Dis(CI x,CI y) {return d[x]+d[y]-(d[LCA(x,y)]<<1);}//询问距离
    		I int Qry(CI x,CI k) {return S.Qry(k,1,n,Rt[x]);}//询问子树内相对顺序第k大的点的编号
    }T;
    class CompressionTree//压缩后的大树
    {
    	private:
    		#define GetPos(x,i,p) (i=upper_bound(L+1,L+Vt+1,x)-L-1,p=T.Qry(Rt[i],(x)-L[i]+1))//求出复制树编号以及接入的节点编号
    		int ee,lnk[N+5],d[N+5],f[N+5][LN+5];struct edge {int to,nxt,val;}e[N+5];
    		int Vt,Rt[N+5],G[N+5];LL tot,L[N+5],R[N+5],D[N+5];
    		I int LCA(RI x,RI y)//倍增LCA(可直接从上面copy)
    		{
    			RI i;d[x]<d[y]&&(x^=y^=x^=y);
    			for(i=0;d[x]^d[y];++i) (d[x]^d[y])>>i&1&&(x=f[x][i]);if(x==y) return x;
    			for(i=LN;~i;--i) f[x][i]^f[y][i]&&(x=f[x][i],y=f[y][i]);return f[x][0];
    		}
    		I int Jump(RI x,CI t)//从x跳到深度t的祖先
    		{
    			for(RI i=0;d[x]^t;++i) (d[x]^t)>>i&1&&(x=f[x][i]);return x;
    		}
    	public:
    		I void Init() {Rt[Vt=1]=L[1]=1,tot=R[1]=n;}
    		I void Add(CI x,CI y,CI v) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v;}
    		I void Ins(CI x,Con LL& y)//复制一棵树
    		{
    			Rt[++Vt]=x,L[Vt]=tot+1,R[Vt]=(tot+=T[x]);RI f;//L,R记录区间
    			GetPos(y,f,G[Vt]),Add(f,Vt,T(G[Vt])-T(Rt[f])+1);//注意边权
    		}
    		I void dfs(CI x=1)//初始化
    		{
    			RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];
    			for(i=lnk[x];i;i=e[i].nxt) d[e[i].to]=d[f[e[i].to][0]=x]+1,D[e[i].to]=D[x]+e[i].val,dfs(e[i].to);
    		}
    		I LL GetD(Con LL& x,Con LL& y)//询问距离
    		{
    			RI a,b,u,v;GetPos(x,a,u),GetPos(y,b,v);if(a==b) return T.Dis(u,v);RI c=LCA(a,b);//在同一棵树中直接询问
    			RI t;if(a==c&&(a^=b^=a^=b,u^=v^=u^=v),b==c)//为祖先关系
    				return t=Jump(a,d[b]+1),T(u)-T(Rt[a])+D[a]-D[t]+1+T.Dis(G[t],v);//儿子向上跳,并加上儿子接入点和祖先询问点的距离
    			RI A=Jump(a,d[c]+1),B=Jump(b,d[c]+1);//一起向上跳
    			return (T(u)-T(Rt[a])+D[a]-D[A]+1)+(T(v)-T(Rt[b])+D[b]-D[B]+1)+T.Dis(G[A],G[B]);//加上接入点间的距离
    		}
    }S;
    int main()
    {
    	RI Qt,i,x,y;LL u,v;scanf("%d%d%d",&n,&m,&Qt);
    	for(i=1;i^n;++i) scanf("%d%d",&x,&y),T.Add(x,y),T.Add(y,x);T.dfs();//模板树
    	for(S.Init(),i=1;i<=m;++i) scanf("%d%lld",&x,&u),S.Ins(x,u);S.dfs();//大树
    	W(Qt--) scanf("%lld%lld",&u,&v),printf("%lld
    ",S.GetD(u,v));return 0;//询问
    }
    
  • 相关阅读:
    c++ namespace简单用法
    python2编码问题
    python dict()函数 /// logging模块///yield//生成器和迭代器
    python 对于一个字典,根据其value值获取其对应的keys值
    python 包/库/模块
    python __init()__
    python continue 和 break的区别
    python list去重
    linux命令,将一个文件夹中的内容copy到另外一个文件夹
    python 正则(re.compile()/re.findall())
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4539.html
Copyright © 2011-2022 走看看