前言
维护一棵树,支持以下操作:
1.链上求和;
2.链上求最值;
3.链上修改;
4.子树修改;
5.子树求和;
6.换根;
7.断开树上一条边;
8.连接两个点,保证连接后仍然是一棵树。
思想
树链剖分有重链和轻边。LCT也一样,分实(重)边和虚(轻)边。一个节点最多连出一条向儿子的实边,因此实边会聚集成链。根据树链剖分的思想,需要用一种数据结构来维护实边组成的链。树链剖分使用了线段树来维护,但线段树显然很静态。
我们思考可以使用能动态的平衡树——splay。
概念
Preferred Child:偏爱儿子,偏爱儿子与父亲节点同在一棵Splay中,一个节点最多只能有一个偏爱儿子(注意,LCT的偏爱儿子与树链剖分的重儿子迥乎不同,后者是点数最大的儿子,而前者则是随便的);
Preferred Edge:实边,连接父亲节点和偏爱儿子的边;
Preferred Path:偏爱路径,由实边及实边连接的节点构成的链;
Auxiliary Tree:辅助树,由一条偏爱路径上的所有节点所构成的Splay称作这条链的辅助树。每个点的键值为这个点的深度,即这棵Splay的中序遍历是这条链从链顶到链底的所有节点构成的序列。辅助树的根节点的父亲指向链顶的父亲节点,然而链顶的父亲节点的儿子并不指向辅助树的根节点。
ps:实边连起来会组成偏爱路径,偏爱路径之间没有公共点。
树链剖分的重链是固定的,但是LCT的偏爱路径是可以改变的。
若一个不在偏爱路径上的点也视为一条没有实边的偏爱路径,那么偏爱路径之间是用虚边连接的。
基础操作:so、link、if_root
bool so(int x)
{
return son[fat[x]][1]==x;
}
void link(int f,int x,bool d)
{
son[fat[x]=f][d]=x;
}
bool if_root(int x)
{
return !fat[x]||son[fat[x]][so(x)]!=x;
}
核心操作:access
access(x)的真正含义:让x节点不含偏爱儿子,同时x到根节点所有边均为实边。
算法的流程如下:
因为x节点不能含偏爱儿子,先将x旋至其所在splay的根,然后断开右子树(变为虚边)。
接着顺着偏爱路径往上爬,每遇到一条虚边,同样把虚边连向的节点y旋至y所在splay的根然后断开y的右子树(使y不含有偏爱儿子),并把x所在splay接在y的右子树(把虚边改为实边)。
这就完成了access。
void access(int y)
{
int x=0;
while(y)
{
splay(y);
link(y,x,1);
x=y;
y=fat[y];
}
}
重要操作:makeroot
makeroot(x)即为将x变为整棵LCT的根。
算法流程如下:对x进行access,然后观察,我们发现虚边子树会随着依附子树一起选择;而x到根的路径则会在同一棵splay里,且x是深度最大的点。
void makeroot(int x)
{
access(x);
splay(x);
fan(x);
}
容易看出,makeroot操作的复杂度与access一致。