ZJOI2016 大森林
大森林
小Y家里有一个大森林,里面有 \(n\) 棵树,编号从 \(1\) 到 \(n\)。一开始这些树都只是树苗,只有一个节点,标号为 \(1\)。这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力。小Y掌握了一种魔法,能让第 \(l\) 棵树到第 \(r\) 棵树的生长节点长出一个子节点。同时她还能修改第 \(l\) 棵树到第 \(r\) 棵树的生长节点。她告诉了你她使用魔法的记录,你能不能管理她家的森林,并且回答她的询问呢?
Input
第一行包含 \(2\) 个正整数 \(n,m\),共有 \(n\) 棵树和 \(m\) 个操作。接下来 \(m\) 行,每行包含若干非负整数表示一个操作,操作格式为:
0 l r
表示将第 \(l\) 棵树到第 \(r\) 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 \(0\) 号操作叶子标号加 \(1\)(例如,第一个 \(0\) 号操作产生的子节点标号为 \(2\)), \(l\) 到 \(r\) 之间的树长出的节点标号都相同。保证 \(1≤l≤r≤n\)。1 l r x
表示将第 \(l\) 棵树到第 \(r\) 棵树的生长节点改到标号为 \(x\) 的节点。对于 \(i (l≤i≤r)\) 这棵树,如果标号 \(x\) 的点不在其中,那么这个操作对该树不产生影响。保证 \(1≤l≤r≤n\),\(x\) 不超过当前所有树中节点最大的标号。2 x u v
询问第 \(x\) 棵树中节点 \(u\) 到节点 \(v\) 点的距离,也就是在第 \(x\) 棵树中从节点 \(u\) 和节点 \(v\) 的最短路上边的数量。保证 \(1≤x≤n\),这棵树中节点 \(u\) 和节点 \(v\) 存在。
\(n\leq 10^5,m\leq 2\times 10^5\)。
Output
输出包括若干行,按顺序对于每个小Y的询问输出答案
Sample Input
5 5
0 1 5
1 2 4 2
0 1 4
2 1 1 3
2 2 1 3
Sample Output
1
2
题解
这题给出了两条\(10^5\)级别的坐标轴,一条是时间轴,一条是树的编号的轴。
显然我们需要进行一些操作使得我们同一时间内只处理一条轴。
性质分析
0 l r
每次操作时长出的节点编号是按操作数来分配的;而如果我们往一棵树上增添一些冗余节点的话,原有两点之间的距离是不会变的。所以我们可以认为每次操作都是全局进行的,即\(\forall [l,r]=[1,n]\)。
1 x u v
的答案与询问时间无关,即它没有时效性。所以我们可以把它看成对最终形态的树的询问。
那么我们可以先处理树形态,再处理询问。
这上面两个操作其实都在提示我们离线。
2 l r x
这个操作比较棘手。我们先考虑它对一棵树的影响。
显然每个操作影响了节点编号属于一个后缀 \([l_i,n]\) 的那些节点的生长点。并且假设有多个操作同时作用于一棵树的话,那么一个节点的生长点是所有影响它的2操作中最晚进行作用的。
也就是说,生长点的规定其实是如下图这样的划分:
如果我们去掉其中某个2操作的影响的话,其实就是把其中一段的生长点规定变成它上一段的生长点规定。注意这样做造成的变化不大,所以说撤销操作是可以进行的。
所以我们可以把树的编号的轴当作时间轴,那么2操作相当于在 \(time=l_i\) 的时候加入它的影响,在 \(time=r_i+1\) 的时候删除它的影响。用上面讲述的方式进行删除即可。
算法设计
用扫描线来处理我们假想的时间轴。
给每个规定生长点的操作建一个权值为0的虚点方便转接,所有被影响的节点就往它之前的最后一个虚点上长。
一个虚点的作用区间结束后就把这个虚点接到上一个更换的生长点所对应的虚点上。
LCT可以很方便的实现,时间复杂度\(O(m \log n)\)。
代码实现
平常我们计算两点间的距离的时候是去统计每个点的父亲边。只需要makeroot
,access
,splay
,取size-1。
但是这题里面我们不能这样做。因为存在着虚点,而虚点的父亲边实际是不存在的,所以虚点不应该计入答案。
如果仅仅是把实点(虚点)权值赋成1(0)的话,由于在LCA处不知道这是实点还是虚点,不知道得出的距离(权值和)应不应该-1,到头来还是要求LCA。
不如直接就用x,y,LCA到根的距离(权值和)容斥,这样做还用不着写makeroot
和rev标记。
如何求LCA呢?方法是先access(x)
,后access(y)
,access(y)
时最后合并的那条链的链尾就是LCA了。
co int N=4e5+7;
int n,m,v[N],ch[N][2],p[N],sum[N],ans[N];
#define lc ch[x][0]
#define rc ch[x][1]
bool nroot(int x){return ch[p[x]][0]==x||ch[p[x]][1]==x;};
void update(int x){sum[x]=sum[lc]+v[x]+sum[rc];}
void rotate(int x){
int y=p[x],z=p[y],l=x==ch[y][1],r=l^1;
if(nroot(y)) ch[z][y==ch[z][1]]=x;p[x]=z;
ch[y][l]=ch[x][r],p[ch[x][r]]=y;
ch[x][r]=y,p[y]=x;
update(y),update(x);
}
void splay(int x){
for(int y,z;nroot(x);rotate(x)){
y=p[x],z=p[y];
if(nroot(y)) x==ch[y][1]^y==ch[z][1]?rotate(x):rotate(y);
}
}
int access(int x){
int t=0;
for(;x;x=p[t=x])
splay(x),rc=t,update(x);
return t;
}
void lik(int x,int y){
splay(x),p[x]=y;
}
void cut(int x){
access(x),splay(x),p[lc]=0,lc=0,update(x);
}
int qry(int x,int y){
int ans=0;
access(x),splay(x),ans+=sum[x];
int t=access(y);splay(y),ans+=sum[y];
access(t),splay(t),ans-=2*sum[t];
return ans;
}
struct node{
int pos,op,x,y;
bool operator<(co node&q)co{
return pos^q.pos?pos<q.pos:op<q.op;
}
}q[N];
int tot,last,id[N],idd,cnt,L[N],R[N],qs;
void newnode(int x){
++tot,v[tot]=x,sum[tot]=x;
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(m);
newnode(1),idd=1,L[1]=1,R[1]=n,id[1]=1;
newnode(0),last=2,lik(2,1);
for(int ti=1,o,l,r,x,u,v;ti<=m;++ti){
read(o);
if(o==0){
read(l),read(r);
newnode(1);
L[++idd]=l,R[idd]=r,id[idd]=tot;
q[++cnt]=(node){1,ti-m,tot,last};
}
else if(o==1){
read(l),read(r),read(x);
l=std::max(l,L[x]),r=std::min(r,R[x]);
if(l<=r){
newnode(0),lik(tot,last);
q[++cnt]=(node){l,ti-m,tot,id[x]};
q[++cnt]=(node){r+1,ti-m,tot,last};
last=tot;
}
}
else{
read(x),read(u),read(v);
q[++cnt]=(node){x,++qs,id[u],id[v]};
}
}
std::sort(q+1,q+cnt+1);
for(int i=1,j=1;i<=n;++i)
for(;j<=cnt&&q[j].pos==i;++j){
if(q[j].op<=0) cut(q[j].x),lik(q[j].x,q[j].y);
else ans[q[j].op]=qry(q[j].x,q[j].y);
}
for(int i=1;i<=qs;++i) printf("%d\n",ans[i]);
return 0;
}