Luogu3348 [ZJOI2016]大森林
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\times 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\n",ans[i]);
return 0;
}