动态树维护一个由若干无序的有根树组成的森林,支持对某个节点到根的路径操作,以及对某个节点的子树操作,还支持换根、加/减边、子树合并/分离等。Link-Cut Trees(LCT)是最常见的一种解决动态树问题的工具。
LCT为虚实链剖分,虚实链式动态变化的,每个实链都由一棵伸展树维护,所有伸展树就像一个森林,由续编连接在一起组成一棵LCT
LCT性质
(1)一个节点到其子节点最多有一个实边,其它均为虚边。(虚边为单向维护父节点信息不维护子节点信息)
(2)每棵伸展树都维护一条按原树深度严格递增的实链
(3)每个节点都被包含且仅被包含在一棵伸展树中。
LCT伸展树之间的连接“认父不认子”,实链形成的伸展树的虚边一定连向原树中该实链的父节点,但该子节点并不一定为原树中父节点的直接儿子。
LCT具有动态变化性:
(1)LCT中的虚实边是动态变化的;
(2)伸展树也是动态变化的,可以随时旋转,只要满足原树中的节点深度有序即可。(即中序遍历输出单个伸展树中的节点,其在原树中的深度是有序的)
LCT基本操作
1、access(x)
access(x)是动态树所有操作的基础,用于打通x到原树根节点的一条实链。即将x到树根的路径变为实链,这条实链上的节点与其他子节点的边变虚边。
操作过程如下
(1)将x旋转为所在伸展树的根,将其右儿子置空(变为虚边)。
(2)通过x向其他伸展树连的虚边得到父节点,将该节点旋转为所在伸展树的根,将右儿子置为前一个节点。
(3)重复第(2)步直至处理到根所在的伸展树。
void access(int x)
{
for(int t=0;x;t=x;x=fa[x])
splay(x),c[x][1]=t,update(x);
}
2、makeroot(x)
access(x)只是打通x到原树根节点的一条实链,有时不能满足需要,很多时候都需要获取指定两个节点x,y之间的路径信息。然而x-y路径上的节点可能不在一棵伸展树中,不能按深度严格递增,此时需要换根操作,将指定的点x换成原树的根。
(1)access(x),打通一条x到原树根的实链;
(2)splay(x),将x旋转为所在伸展树的根。
(3)reverse(x),反转当前神转述的左右子树,使所有节点的深度倒过来(中序遍历从大到小)。
void makeroot(int x)
{
access(x);splay(x);rev[x]^=1;
}
3、findroot(x)
findroot(x)表示查找x所在原树的树根,主要用来判断两点之间的连通性。若,则表明x、y在同一棵树中。
查找根的操作分为3步
(1)access(x),打通一条x到原树根的实链
(2)splay(x),将x旋转为所在伸展树的根
(3)查找当前伸展树的最左节点(深度最小的点,即树根),返回根节点即可
int findroot(int x)
{
access(x);splay(x);
while(c[x][0]) x=c[x][0];
return x;
}
4、split(x,y)
split(x,y)表示分离出x-y的路径为一条实链,用一个伸展树维护。
分离操作分为3步
(1)makeroot(x),将x变成原树的根
(2)access(y),打通一条y到原树根的实链
(3)splay(y),将y旋转到当前伸展树的树根
void split(int x,int y)
{
makeroot(x),access(y),splay(y);
}
5、link(x,y)
link(x,y)表示在x,y之间连接一条边。若x,y之间连通,则不可以连边。连边操作分为2步
(1)makeroot(x),将x变为原树的根
(2)将x的父节点修改为y
void link(int x,int y)
{
makeroot(x);fa[x]=y;
}
6、cut(x,y)
cut(x,y)表示将x-y的边断开(删边)。若x,y之间不连通,则不可以删边。若x,y之间连通,则还要判断两者之间是否直接相连,若不是同样不可删。
删边分为3步
(1)split(x,y),分李处x-y的路径为一条重链,若x不是y的左儿子或者x有右子树,则说明x与y之间有其他节点,不能删边
(2)将x-y的边断开,修改y的左儿子为0,y的左儿子x的父节点为0
(3)update(y),更新y的相关信息。
void cut(int x,int y)
{
split(x,y);
if(c[y][0]!=x || c[x][1]) return;
c[y][0]=fa[x]=0;
update(y);
}
7、isroot(x)
isroot(x)表示判断x是否为所在伸展树的根。注意:伸展树的根和原树的根不是一回事。伸展树的根与其父节点是一条虚边,但并不代表其在原树上是父节点的左右儿子之一,但一定是其子树中的一个节点。
只需要判断x的父节点的儿子信息是否为x即可(是否为虚边)
bool isroot(int x)
{
return c[fa[x]][0]!=x && c[fa[x]][1]!=x;
}