link-cut-tree 是一种维护森林的数据结构,可以在log的时间内完成修改、查询链上信息等操作 。
实链剖分
我们知道,树链剖分通过划分轻重链,保证了一个点到根最多有log跳轻链和log条重链从而保证复杂度为log。
而LCT通过把每条边划分成实边和虚边,把整棵树拆成若干部分,每部分在原树中是深度严格递增的一条链(相当于是自上而下的一条链)。
而我们的每一部分都是用splay去维护的,而在每棵splay中点的深度也是严格递增的。
对于每一条实边,父亲都有连向儿子的一条边,儿子也有连向父亲的边。
对于每一条虚边,只有儿子连向父亲的边。
下面我们了解一下LCT的各种操作。
access
我们定义access(x)为,打通一条从x到整颗树的根的一条实链。
打通之后,x所在的splay中只有x到根这条链上的的所有点。
首先我们明确,对于一颗splay中某个点x,ch[x][0]中所有点的深度都是比它小的,ch[x][1]中所有点的深度都是比它大的。
若fa[x]=y那么y的深度一定比x小。
那么首先我们把x splay一下,然后我们把它的右儿子断成虚边,因为这部分我们不希望它出现在最后的splay里(因为x要成为splay里深度最大的点)。
然后我们把指针指向fa[x],令其为y,我们再去splay(y),这是我们也需要把y的右儿子断掉,然后向x连一条重边。
然后上述过程一直重复就好了,代码如下。
inline void access(int x){ for(int y=0;x;y=x,x=fa[x])splay(x),ch[x][1]=y,pushup(x);}
makeroot
我们定义makeroot(x)为把x当做整颗splay的根。
首先我们肯定要access(x),让它和根在一颗splay里。
考虑x成为根之后,整棵树相对深度发生变化的点只存在于这颗splay中,而且相对深度和原来是完全相反的,所以我们只需要在splay(x),使x成为根节点后reverse整颗splay就好了。
代码长这样:
inline void makeroot(int x){access(x);splay(x);rev[x]^=1;}
split
我们定义split(x,y)为找到x到y的一条路径。
如果上面的东西都理解了,这个就好说了。
inline void split(int x,int y){ makeroot(x);access(y);splay(y);}
findroot
我们定义findroot(x)为找到x所在树的根。
我们先access一下,在splay一下,因为根是整颗splay里深度最浅的点,所以我们一直往左儿子找就好了。
注意pushdown
inline int findroot(int x){ access(x);splay(x);pushdown(x); while(ch[x][0])x=ch[x][0],pushdown(x); return x;}
link
我们定义link(x,y)为连接一条x到y的虚边。
我们先makeroot x,直接令fa[x]=y即可,不难理解。
注意判不合法情况
inline void link(int x,int y){ if(findroot(x)===findroot(y))return; makeroot(x);fa[x]=y;}
cut
我们定义cut(x,y)为切断x到y的边。
我们先split出x到y的链。
然后我们要判断当前是否存在x到y的边。
令x为根,y为splay的根,那么ch[y][0]=x,ch[y][1]=0,fa[x]=y若三个条件同时满足那么我们可以切断这条边 。
inline void cut(int x,int y){ if(findroot(x)!=findroot(y))return; split(x,y); if(fa[x]!=y||ch[y][1]||ch[y][0]!=x)return; fa[x]=ch[y][0]=0;}
LCT的概念和基本操作就是这么多,感觉这里面也有很多十分玄妙的地方,不过感性理解就好了。
未完待续。。。