XXI.[ZJOI2016]大森林
论LCT的百种用法系列
这题有几个性质:
1.询问与时间无关。因为只是添加新点,原来点之间的位置关系不变。因此只要询问的两个点都被建出来了,何时进行询问并无影响。
2.操作重叠的部分很多。因为我们不管怎么加点,即使是加原树中没有的点,仍然有原来点之间的位置关系不变。因此,我们每次加点操作都可以默认是所有树全都加点,不需要管原本\(l,r\)的限制。这样的话,某些相似的部分可能在很多树上都出现了。我们是否可以把相似的部分提取出来呢?
我们考虑差分对于生长节点的修改。
对于每次修改,我们都新建一个虚点。在下一次修改之前,所有的生长效果都是在这个虚点上再挂上一个点。这样的话,就在更改生长节点时,就可以直接虚点一断一连即可。
生长操作就是这段代码:
if(t1==0)insnewreal(t2,t3),link(cntofall,lastvir);//a new real node, linked to the previous virtual
对于修改操作:
首先,关注到原题中的一段话:
\(1\ l\ r\ x\) 表示将第 \(l\) 棵树到第 \(r\) 棵树的生长节点改到标号为 \(x\) 的节点。对于 \(i\) \((l\leq i\leq r)\) 这棵树,如果标号 \(x\) 的点不在其中,那么这个操作对该树不产生影响。
因为我们的生长都是所有树全都长,那就意味着有些原本没有\(x\)点的树也长出了\(x\)点。
因此,我们这个修改区间应该与\(x\)号节点的生长区间取交集。如果交集为空,直接跳过此次修改。
修改操作步骤如下:
-
取并集
-
新建代表此次修改的虚点,并将其父亲设为上次修改的虚点(即\(link\)一下)。
-
在树\(l\)处压入操作“将这次修改的虚点连到这次修改的目标生长节点上”(这应用了扫描线的思想)。
-
在树\(r+1\)处压入操作“将这次修改的虚点重新连回上次修改的虚点”。
-
将“上次修改的虚点”这一统计变量赋成本次修改的虚点。
对应代码:
if(t1==1){
scanf("%d",&t4);
t2=max(t2,nl[t4]);
t3=min(t3,nr[t4]);
if(t2>t3)continue;
link(++cntofall,lastvir);//add a new virtual node
v[t2].push_back(op(-1,cntofall,realid[t4]));//initially,the new virtual is linked to lastvir;after TREE t2, it is now linked to realid[t4];
v[t3+1].push_back(op(-1,cntofall,lastvir));//and after t3+1, the new virtual is linked to lastvir again.
lastvir=cntofall;
}
然后就是按照从\(1\)到\(n\)的顺序遍历每棵树,依次进行修改和询问了。
哦,对,询问,怎么办呢?
首先这题我们最好不要写无根LCT,即使用\(makeroot\)的LCT。因为操作有点多,这个无根LCT的\(makeroot\)常数极大,按照我这种不写快读的写法是妥妥T的。那么我们应该如何实现查询路径长度呢?
首先一个非常合情合理的想法就是树上差分。假定我们要查询的是点对\((x,y)\),我们就找到\(LCA_{x,y}\),并尝试用一些值拼出路径长度。
假设我们已经找出\(LCA_{x,y}\),则我们只需要在每个节点维护splay中实子树大小,然后\(access(x),access(y),access(LCA)\),分别求出\(x\)到\(ROOT\)、\(y\)到\(ROOT\)、\(LCA\)到\(ROOT\)的路径长度(当然,只包括实点,虚点不算\(size\))。答案即为\(s_x+s_y-2s_{LCA}\)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define lson t[x].ch[0]
#define rson t[x].ch[1]
int n,m,cntofreal,cntofall,cntofquery,lastvir,realid[200100],nl[200100],nr[200100],res[200100];
struct LCT{
int fa,ch[2],sz,real;
}t[1001000];
inline int identify(int x){
if(x==t[t[x].fa].ch[0])return 0;
if(x==t[t[x].fa].ch[1])return 1;
return -1;
}
inline void pushup(int x){t[x].sz=t[lson].sz+t[rson].sz+t[x].real;}
inline void rotate(int x){
register int y=t[x].fa,z=t[y].fa,dirx=identify(x),diry=identify(y),b=t[x].ch[!dirx];
if(diry!=-1)t[z].ch[diry]=x;t[x].fa=z;
if(b)t[b].fa=y;t[y].ch[dirx]=b;
t[y].fa=x,t[x].ch[!dirx]=y;
pushup(y),pushup(x);
}
inline void splay(int x){for(;identify(x)!=-1;rotate(x))if(identify(t[x].fa)!=-1)rotate(identify(x)==identify(t[x].fa)?t[x].fa:x);}
inline int access(int x){register int y=0;for(;x;x=t[y=x].fa)splay(x),rson=y,pushup(x);return y;}
inline int findroot(int x){access(x),splay(x);while(lson)x=lson;splay(x);return x;}
inline void link(int x,int y){access(x),splay(x),t[x].fa=y;}
inline void cut(int x){access(x),splay(x),lson=t[lson].fa=0,pushup(x);}
inline int getdis(int x,int y){
int sx,sy,sl,lca;
access(x),splay(x),sx=t[x].sz;
lca=access(y),splay(y),sy=t[y].sz;
access(lca),splay(lca),sl=t[lca].sz;
return sx+sy-2*sl;
}
void insnewreal(int l,int r){//insert a new node for all trees in section [l,r]
t[realid[++cntofreal]=++cntofall].real=true;//cnt of all,the ID of all nodes in the LCT; real id,the ID of all the real nodes in the LCT
nl[cntofreal]=l,nr[cntofreal]=r;//cnt of real, the ID of added real nodes
}
struct op{
int id,x,y;
op(int a=0,int b=0,int c=0){id=a,x=b,y=c;}
};
vector<op>v[100100];
int main(){
scanf("%d%d",&n,&m);
insnewreal(1,n);//initially, one real node for all
link(++cntofall,1);//a initial virtual node, linking to the initial real node
lastvir=cntofall;//last virtual, the previous virtual node's ID.
for(int i=1,t1,t2,t3,t4;i<=m;i++){
scanf("%d%d%d",&t1,&t2,&t3);
if(t1==0)insnewreal(t2,t3),link(cntofall,lastvir);//a new real node, linked to the previous virtual
if(t1==1){
scanf("%d",&t4);
t2=max(t2,nl[t4]);
t3=min(t3,nr[t4]);
if(t2>t3)continue;
link(++cntofall,lastvir);//add a new virtual node
v[t2].push_back(op(-1,cntofall,realid[t4]));//initially,the new virtual is linked to lastvir;after TREE t2, it is now linked to realid[t4];
v[t3+1].push_back(op(-1,cntofall,lastvir));//and after t3+1, the new virtual is linked to lastvir again.
lastvir=cntofall;
}
if(t1==2)scanf("%d",&t4),v[t2].push_back(op(++cntofquery,realid[t3],realid[t4]));
}
for(int i=1;i<=n;i++)for(int j=0;j<v[i].size();j++){
if(v[i][j].id!=-1)res[v[i][j].id]=getdis(v[i][j].x,v[i][j].y);
else cut(v[i][j].x),link(v[i][j].x,v[i][j].y);
}
for(int i=1;i<=cntofquery;i++)printf("%d\n",res[i]);
return 0;
}