把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4573】[ZJOI2016] 大森林(LCT)

点此看题面

大致题意:\(n\)棵树,初始各有\(1\)个编号为\(1\)的节点,且其为生长节点。\(3\)种操作:将\([l,r]\) 区间内的树增加一个新的编号的节点,修改 \([l,r]\)区间内的树生长节点(没有该节点的树忽略此操作),询问某一棵树上两点的距离(保证存在)。

考虑离线

不难发现,无论什么操作,都不会改变原有树的形态,因此,询问的答案是不会变的。

因此,就不难想到离线,从\(1\)\(n\)枚举每棵树,每次只考虑相邻两棵树之间的差异,然后修改并进行询问。

这也是做这道题的一个基础思想。

而要进行修改,就需要使用\(LCT\)

又由于这题树的形态固定,因此要用不换根\(LCT\)

考虑增加节点操作

接下来我们考虑增加节点操作。

不难发现,其实我们把对\([l,r]\)增加节点改成对\([1,n]\)增加节点,其实对答案不会有任何影响。

因为你增加节点与询问时,都不可能对这个并不实际存在的点进行操作,因此这个点即使存在,也是完全多余的,不会造成任何影响。

但是要注意题目中的提醒,在修改生长节点时一定不能修改没有这个点的树。

这其实也只要记一下每个点实际存在的区间,修改时将修改左边界与实际存在的左边界取\(max\),修改右边界与实际存在的右边界取\(min\)即可。

考虑询问操作

暂时先不考虑修改生长节点的操作,我们先考虑之前提到的,在知道当前树的情况下,如何求两点距离,即如何处理询问。

这时就要用到一个神奇的技巧:\(Access\)\(LCA\)

假设要在\(LCT\)中求\(x,y\)\(LCA\),则我们先\(Access(x)\),此时\(x\)到根的路径上的边都变成了实边。

然后\(Access(y)\),此时\(y\)到根的路径上的边都变成了实边。

\(LCA(x,y)\),不难发现应该是在两次操作中,都可以从根出发只走实边到达的深度最大的节点。

这其实就是我们在\(Access(y)\)时最后操作的节点!(具体实现可以见代码)

而求出了\(LCA\),只要用\(Depth_x+Depth_y\)减去\(2Depth_{LCA}\)即可。

\(Depth\)怎么求呢?

我们可以对于\(LCT\)\(Splay\)中的每个点,记录下它子树内实节点的个数(之所以要说实节点,是因为为了处理修改生长节点的操作,我们需要增加虚节点,这在后文有详细解释)。

而在\(Access\)\(Splay\)\(x/y/LCA\)之后,此时以其为根的\(Splay\)维护的就是从根节点到其路径上的信息,而此时\(Splay\)内的实节点数,其实就相当于它的深度!

这样一来,询问操作就处理完了。

考虑修改生长节点操作

这应该是最烦的,也是比较难理解的一个操作了。

考虑我们修改生长节点之后,其实也就相当于在下一次修改生长操作之前,所有被修改生长节点的树被新加入的节点,父亲就从原先生长节点变成了新的生长节点。

而考虑没有被修改生长节点与被修改生长节点的两棵树的差异,其实也就是这些点的位置发生了变化。

如果我们要按照之前提到的离线思想,我们每次修改时,就相当于要将这一坨节点从一个节点的儿子被移作另一个节点的儿子。

而要快速移动的话,就需要新建一个虚点,然后将这些节点全部作为这个虚点的儿子,并将虚点作为目标节点的儿子,这样要移动儿子直接将这个虚点在\(LCT\)\(Cut\)\(Link\)一下即可。

而具体实现中还有一定细节,可参考代码。

代码

#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 M 200000
#define swap(x,y) (x^=y^=x^=y)
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define pb push_back
using namespace std;
int n,m,RtoA[M+5],IsReal[M+5],L[M+5],R[M+5],ans[M+5];
struct Op {int p,x,y;I Op(CI t=0,CI a=0,CI b=0):p(t),x(a),y(b){}};
vector<Op> v[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C,stdout);}
}F;
class LinkCutTree//不换根LCT
{
	private:
		#define PU(x) (O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+IsReal[x])
		#define Re(x) (swap(O[x].S[0],O[x].S[1]),O[x].R^=1)
		#define PD(x) (O[x].R&&(Re(O[x].S[0]),Re(O[x].S[1]),O[x].R=0))
		#define Wh(x) (O[O[x].F].S[1]==x)
		#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
		#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
		int St[M+5];struct node {int Sz,R,F,S[2];}O[M+5];
		I void Ro(RI x) 
		{
			RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x),
			O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1),PU(f);
		}
		I void S(RI x) 
		{
			RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;
			W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(f)^Wh(x)?x:f),0),Ro(x);PU(x);
		}
		I int Ac(RI x) {RI s;for(s=0;x;x=O[s=x].F) S(x),O[x].S[1]=s,PU(x);return s;}//此处Access返回最后操作的节点编号,用于求LCA
	public:
		I void Link(CI x,CI y) {S(x),O[x].F=y;}//连边
		I void Cut(CI x) {Ac(x),S(x),O[x].S[0]=O[O[x].S[0]].F=0,PU(x);}//删掉与父亲的边
		I int GetDis(CI x,CI y)//求距离
		{
			RI t,dx,dy;Ac(x),S(x),dx=O[x].Sz,t=Ac(y),S(y),dy=O[y].Sz;//求出Depth[x]和Depth[y],同时求出LCA(x,y)
			return Ac(t),S(t),dx+dy-(O[t].Sz<<1);//求出Depth[LCA],从而计算x与y的距离
		}
}LCT;
#define BuildR(x,y) (IsReal[RtoA[++CR]=++CA]=1,L[CR]=x,R[CR]=y)//新建一个实际存在于[l,r]树内的实点
int main()
{
	freopen("forest.in","r",stdin),freopen("forest.out","w",stdout);
	RI i,j,sz,op,x,y,z,lstV,CA=0,CR=0,Qcnt=0;
	for(F.read(n,m),BuildR(1,n),LCT.Link(lstV=++CA,CR),i=1;i<=m;++i)//初始化,建立实点1,并给1建一个虚点
	{
		switch(F.read(op,x,y),op)
		{
			case 0:BuildR(x,y),LCT.Link(CA,lstV);break;//增加节点,在LCT中与上一个虚点连边,方便转移
			case 1:
				if(F.read(z),Gmax(x,L[z]),Gmin(y,R[z]),x>y) continue;//如果要修改的区间为空,则跳过
				LCT.Link(++CA,lstV),v[x].pb(Op(-1,CA,RtoA[z])),v[y+1].pb(Op(-1,CA,lstV)),
				lstV=CA;break;//新建虚点,并存下此操作
			case 2:F.read(z),v[x].pb(Op(++Qcnt,RtoA[y],RtoA[z]));break;//存下此操作
		}
	}
	for(i=1;i<=n;++i) for(sz=v[i].size(),j=0;j^sz;++j)//从左到右扫描每一棵树
		~v[i][j].p?ans[v[i][j].p]=LCT.GetDis(v[i][j].x,v[i][j].y)://处理询问
		(LCT.Cut(v[i][j].x),LCT.Link(v[i][j].x,v[i][j].y),0);//更改生长节点
	for(i=1;i<=Qcnt;++i) F.writeln(ans[i]);return F.clear(),0;//输出答案
}
posted @ 2019-03-27 19:51  TheLostWeak  阅读(507)  评论(0编辑  收藏  举报