Luogu3348 [ZJOI2016]大森林

Link

Solution

首先不能看错题。题目里有句话:子节点的标号为上一个 0 号操作叶子标号加 1

它是站在全局0号操作的角度的,比如上一次在([l_0,r_0])长了一个编号为(i)的节点,这一次0操作是在([l,r]),那么长出的节点应该是(i+1)。这样意味着某个编号的节点只有它那一轮操作的([l,r])范围内的树有。也就是说每棵树终态节点编号是不一定连续的。

而我把它理解成了这棵树中上一次0号操作的标号......(这种错误理解下每棵树终态节点编号都是连续的)。而且样例居然刚好契合这种理解!吐了

正确理解了题意后,就会好做一些。有几个性质:

  • 因为编号为(i)的节点只在第(i-1)轮0号操作出现,而且保证询问是合法的(询问树上有的节点)。所以可以把每次操作的([l,r])扩展成([1,n])
  • 可以发现1号操作换生长节点并不会影响树的结构。也就是说每个0号操作只受时间在它之前的1号操作的影响。它一定接在它前面离它最近的在该树生效的,1号操作的生长节点下面。

维护每棵树的形态显然是不行的。考虑从左往右遍历第1棵到第n棵树,发现两棵树之间的变化实际上就是插入、删除一些0,1号操作。所以可以维护两棵树之间的变化,即已经有第(i-1)棵树,想办法把它变成第(i)棵树。

所以现在要考虑的问题是快速修改连边情况,可以用LCT+虚点来维护。

先构造图:

 1.对全局的每个1操作按时间顺序建立虚点。每个虚点连向前一个虚点。第一个虚点连向1号实点,1号实点是定根。

 2.把0操作生成的实点连向时间上离它最近的那个1操作虚点(每个实点看作全局生成,不再是[l,r])。

对于每个1操作,如果它在当前节点生效,那就把它连向对应的生长节点;如果失效,就连向前一个虚点。

这样的话更换生长节点,修改一下虚点的连边就可以转移所有实点的连边。

因为不能每次都遍历所有1操作看是否生效,所以可以考虑差分,让其在(l)生效,在(r+1)失效。

还有一个问题是求两点之间路径长度不能在(split)出来了,因为这样需要(makeroot),但是维护的是有根树,直接(makeroot)会破坏原树结构。

还是考虑差分。答案是(dis[a]+dis[b]-2 imes dis[lca(a,b)])

(dis)是到根节点路径,可以(access,splay)来求。

(lca)同样可以在(LCT)上求出!考虑先(access(a)),那么(a)到根的路径已经在一棵(splay)中了。这时再(access(b)),那么到达根所在的(splay)时经过的那条虚边,指向的父节点,就是(lca(a,b))

inline int access(int x){
		int y=0;
		for(;x;y=x,x=fa[x])
			splay(x),ch[x][1]=y,pushup(x);
		return y;
	}

(access(a))之后再(access(b)),最后的那个(y)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define REP(i,a,b) for(int i=(a),ed=(b);i<=ed;++i)
inline int read(){
	register int x=0,f=1;register char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
	while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
	return f?x:-x;
}

const int M=2e5+10,_=M<<1;
int n,m,rp,vp,L[M],R[M],cnt,ans[M],ans_cnt;
struct Squery{int type,p,x,y,id;inline bool operator<(const Squery &a)const{return p==a.p?type<a.type:p<a.p;}}q[_];

namespace LCT{
	int fa[_],ch[_][2],s[_],v[_];
	inline void pushup(int x){s[x]=s[ch[x][0]]+s[ch[x][1]]+v[x];}
	inline int getdir(int x){return x==ch[fa[x]][1];}
	inline bool noroot(int x){return x==ch[fa[x]][0]||x==ch[fa[x]][1];}
	inline void rotate(int x){
		int f=fa[x],p=fa[f],k=getdir(x),s=ch[x][!k];
		ch[x][!k]=f;ch[f][k]=s;if(noroot(f))ch[p][getdir(f)]=x;
		fa[f]=x;fa[x]=p;if(s)fa[s]=f;
		pushup(f);
	}
	inline void splay(int x){
		for(int f=fa[x];noroot(x);rotate(x),f=fa[x])
			if(noroot(f))rotate(getdir(f)==getdir(x)?f:x);
		pushup(x);
	}
	inline int access(int x){
		int y=0;
		for(;x;y=x,x=fa[x])
			splay(x),ch[x][1]=y,pushup(x);
		return y;
	}
	inline void cut(int x){
		access(x);splay(x);
		ch[x][0]=fa[ch[x][0]]=0;
		pushup(x);
	}
	inline void link(int x,int y){
		access(x);splay(x);fa[x]=y;
	}
}using namespace LCT;

int main(){
	n=read(),m=read();vp=m;
	L[1]=1,R[1]=n;
	link(++vp,++rp);
	REP(t,1,m){
		int op=read();
		if(!op){
			++rp;L[rp]=read(),R[rp]=read();v[rp]=1;
			link(rp,vp);
		}
		else if(op==1){
			int l=read(),r=read(),x=read();
			l=max(l,L[x]),r=min(r,R[x]);
			if(l>r)continue;
			++vp;link(vp,vp-1);
			q[++cnt]=(Squery){0,l,vp,x,0};
			q[++cnt]=(Squery){0,r+1,vp,vp-1,0};
		}
		else{
			int x=read(),u=read(),v=read();
			q[++cnt]=(Squery){1,x,u,v,++ans_cnt};
		}
	}
	sort(q+1,q+cnt+1);
	REP(t,1,cnt){
		if(q[t].type){
			int res=0,x=q[t].x,y=q[t].y,p;
			access(x);splay(x);res+=s[x];
			p=access(y);splay(y);res+=s[y];
			access(p);splay(p);res-=s[p]*2;
			ans[q[t].id]=res;
		}
		else{
			cut(q[t].x);link(q[t].x,q[t].y);
		}
	}
	REP(i,1,ans_cnt)printf("%d
",ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/fruitea/p/12144834.html