一般我们做树上的某些奇奇怪怪的值都是用树链剖分加线段树,这已经可以维护许多东西了。
可是这个算法有一个缺陷,就是如果树的形态会发生改变的话,就不用玩了。
动态树呢是一类牛逼的问题。
而lin-cut-tree就是我们处理这类问题的利器。
lct的基础是splay,所以如果对splay一脸懵逼的先去学splay,至少会区间翻转。
戳这!
在这里我换一个顺序解释,希望读者能够更快地懂。
先看lct的核心操作:access
void access(int x) {
int y = 0;
while(x) {
splay(x, 0); fa[t[x][1]] = 0; pf[t[x][1]] = x;
t[x][1] = y; fa[y] = x; pf[y] = 0;
update(x); y = x; x = pf[x];
}
}
access的作用是把x到根这条路径设为偏爱路径,偏爱路径上的点叫偏爱点,一条偏爱路径上的偏爱点都在一颗splay里,splay的关键字是深度。
偏爱点有一个非常重要的约束,就是它的子节点中只能有一个是偏爱点,也就是说,不同的两条偏爱路径是不会有公共点的。
一棵树会被偏爱路径分解,偏爱路径一定是自上而下的一条路径。
当然有些点不是偏爱点,不在偏爱路径上。
fa[x]表示x在splay中的父亲,pf[x]表示x在原树中,第一个不是偏爱路径连向的点。
access操作中,x到根这一路径上也许有的点本身就是偏爱点,我们需要把那些点所在的偏爱路径与x到根这一路径断掉。
x沿pf往上跳,因为有些点本身就在一颗splay里,我们没有必要修改。
splay(x,0)后,x为当前splay的根,它的右子树是深度比它大的点,自然要断掉,而右子树的pf就是x。
这样一次access的时间复杂度就是pf了多少次。
tajan说这个是log的,tajan又说splay的复杂度是log的,都是均摊log的,所以log^2。
splay的话需要加一句话:
void rotate(int x) {
int y = fa[x], k = lr(x);
t[y][k] = t[x][!k]; if(t[x][!k]) fa[t[x][!k]] = y;
fa[x] = fa[y]; if(fa[y]) t[fa[y]][lr(y)] = x;
t[x][!k] = y; fa[y] = x; pf[x] = pf[y];//注意这里
update(y); update(x);
}
void splay(int x, int y) {
xc(x);
while(fa[x] != y) {
if(fa[fa[x]] != y)
if(lr(x) == lr(fa[x])) rotate(fa[x]); else rotate(x);
rotate(x);
}
}
那splay有什么用呢?
换根:
void makeroot(int x) {
access(x); splay(x, 0); change(x);
}
change的意思是把x所在splay翻转。
因为要翻转,所以要splay。
连边:
void link(int x, int y) {
makeroot(x); pf[x] = y;
}
删边:
void cut(int x, int y) {
makeroot(x);
access(y); splay(y, 0);
t[y][0] = fa[x] = pf[x] = 0;
update(y);
}
好累,要查询x->y的什么什么的话。
makeroot(x),access(y),splay(x,0)
这样x所在的splay就是x->y路径上的所有点了。
于是lct讲完了,重在理解(呵呵~~)。