就是动态的树链剖分,可以用来维护森林。
link cut tree本蒟蒻很早就学过,一直没有打过(自己太颓了太没决心了),最近迎接WC就去码了一波。
类似树链剖分,有重边与轻边,有重链。
其中,树链剖分采用线段树来维护重链,而动态树一般使用splay来维护重链(维护重链的数据结构被称为辅助树,本学习小记中统一把splay当作辅助树)。
注意如果一个点与其所有相连的点间的边都是轻边那么它算是一个点的重链。
对于一条重链的splay,其大小关系是:左子树所有结点比当前结点在森林中的深度小,右子树的所有结点比当前结点在森林中的深度大。
对于一条重链,其深度最小点与其父节点的连边是一条轻边(除非该深度最小点是根节点),我们在这条重链的splay的根节点上保存其深度最小点的父节点,记录在数组pp中。
access(x):将x到根节点的路径变为重链,且不可再延伸。
我们先将x选至其所在splay的根,然后为了保证不可再延伸,断掉其与右子树的连边并修改pp。然后接下来每次找到当前x所在重链深度最小点的父节点y,进行合并,直至延伸到根。
void access(int x){
int y;
splay(x,0);
father[tree[x][1]]=0;
if (tree[x][1]) pp[tree[x][1]]=x;
tree[x][1]=0;
update(x);
while (pp[x]!=0){
y=pp[x];
splay(y,0);
father[tree[y][1]]=0;
if (tree[y][1]) pp[tree[y][1]]=y;
tree[y][1]=x;
father[x]=y;
pp[x]=0;
update(y);
splay(x,0);
}
}
makeroot(x):让x变为其所在树的根节点(是树不是splay)
注意到换根后只是x到根节点的路径深度发生了变化,这个变化就是翻转了过来。先access(x),然后打翻转标记。
void makeroot(int x){
access(x);
splay(x,0);
bz[x]^=1;
}
cut(x,y):将x与y间的边断开(需保证一定有边)
先让x成为根节点,然后access(x),之后直接令pp[y]=0(需要将y先选至splay的根因为pp保存在根上)。
void cut(int x,int y){
makeroot(x);
access(y);
splay(y,0);
pp[y]=0;
father[tree[y][0]]=0;
if (tree[y][0]) tree[y][0]=0;
update(y);
}
link(x,y):让x与y之间连一条边(需保证一定无边)
让x成为根节点,access(y),然后直接pp[x]=y(同cut操作x要为splay的根节点)。
void link(int x,int y){
makeroot(x);
access(y);
splay(x,0);
pp[x]=y;
}
以下题目可以在我的blog找到
树的统计(强行把树链剖分打成LCT)
魔法森林