splay: 必须
树链剖分: 可选,知道树链剖分会容易理解一些。
以下大部分图片来自https://blog.csdn.net/saramanda/article/details/55253627
LCT,又叫林克-卡特树,可以用来解决动态树问题。
LCT显然是一棵树,它长这样:
这上面有一些粗一点的边,我们把它称为重边;还有一些细一点的,我们把它称为轻边。(就像树链剖分的定义)
每个点连向儿子的重边最多只能有1条(可以没有),因此所有的重边能构成树上的一条一条链,叫做重链。
连到重边的儿子叫做嫡长子**重儿子**。
例如上面这个图,1-2-5, 3-7, 4, 6, 8就分别是这棵树的重链。
但是与树链剖分不同的是,LCT中的重边和轻边是可以不断变化的,甚至边/根也可以不断变化,因此我们需要一种灵活一点的数据结构来维护重链,那就是splay啦。
LCT节点的最基本的定义:
struct node
{
node *son[2],*fa;
int rev;
};
一棵splay维护LCT的一条重链,splay维护的关键字是点的深度,一个点深度越浅,在平衡树中的位置就越靠左。
那么LCT就可以视为一棵splay森林,如下:
其中三角形代表一棵splay,也代表了原树中的一条重链。
箭头什么意思呢?箭头指向重链最顶端的点的父亲,但是起始点是代表重链的splay的root,可以理解为splay的父亲,但是splay的父亲不记录它有这个儿子。(这条边是单向的)
例如在第一个图中,3-7这条重链的splay的父亲就是1,但是1的左儿子或右儿子并没有记录3-7这条重链的任何一个点。
那么如何判定一个节点是所在splay的根呢?
我们可以写一个isroot函数,返回1代表是所在splay的根,0代表不是:
int isroot(int x)
{
if(x->fa==NULL)
{
return 1;
}
return !((x->fa->son[0]==x)||(x->fa->son[1]==x));
}
这个词好像没啥好的翻译,不妨称为废嫡立庶。
用法:access(x)
含义:专门打通一条从真实的*LCT树上开辟的从根到x的重链,专门用一棵splay维护。与这条路径上的点相连的任何其他重边都会被强行变成轻边*。
做法:
记录一个y,初始时y为NULL.
取x所在splay,将x旋到根后,删除右儿子。
这样做的目的是为了删除x的重儿子。(废嫡)
将y接到x的右儿子上,这样就把x和y所在的重链接到一起了(立庶)
这样就打通了一条从root到x的重链。
代码:
int access(node *x)
{
node *y=NULL;
while(x!=NULL)
{
splay::splay_root(x);
splay::pushdown(x);
x->son[1]=y; //这里用y自动把x的右儿子冲掉了
if(y!=NULL)
{
y->fa=x;
}
//如果LCT维护了信息,记得在这里更新
//splay::updata(x);
y=x;
x=y->fa;
}
return 0;
}
用法:makeroot(x)
含义:将x变成整棵LCT的根,一般用于与access配合,形成打通任意两个点的效果。
方法:
将x变成根,只需要将从root到x的父子关系反向。
首先access(x),这样就有一条从root到x的重链。
如图,可以发现这棵树上root到x的点构成了一棵splay,由于splay的关键字是深度,因此只需要将splay翻转,就将x变成了LCT的根。
代码:
int makeroot(node *x)
{
access(x);
splay::splay_root(x); //将x提到splay的根,这样只需要在x处打一个reverse标记就代表翻转了splay
x->rev^=1;
return 0;
}
用法:link(x,y)
含义:新建一条边x-y,将x所在LCT和y所在LCT连起来。
方法:
首先makeroot(y),这时y所在LCT的根就变成了y,然后直接将y的父亲指向x。
代码:
int link(node *x,node *y)
{
makeroot(x);
x->fa=y;
return 0;
}
用法:cut(x,y)
含义:删除边x-y,必须保证x-y这条边存在。(不存在的话加个特判就可以了)
方法:
将x变成根,access(y),这时x和y所在splay中只有x,y两个点。可以直接断开这两个点的联系。
代码:
int cut(node *x,node *y)
{
makeroot(x);
access(y); //现在x和y的splay中只有x,y两个点了
splay::splay_root(y); //将y作为splay的根
splay::pushdown(y);
y->son[0]=x->fa=NULL; //删掉x和y之间的边
//splay::updata(y); //如果lct维护了信息,记得在这里更新
return 0;
}
用法:getval(x,y)
含义:查询x到y的树上路径中存储的信息
方法:
makeroot(y),access(x),此时x,y的splay中的所有节点就是x-y路径上的节点。
代码:
int getval(node *x,node *y)
{
makeroot(y);
access(x);
splay::splay_root(x);
return x->val;
}
BZOJ 2049 [Sdoi2008]Cave 洞穴勘测
这题只有link和cut两个操作,比较水
BZOJ 3282 Tree
LCT真板子题
Qtree系列
据说都可以用LCT写?