lct学习小记

一般我们做树上的某些奇奇怪怪的值都是用树链剖分加线段树,这已经可以维护许多东西了。
可是这个算法有一个缺陷,就是如果树的形态会发生改变的话,就不用玩了。
动态树呢是一类牛逼的问题。
而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讲完了,重在理解(呵呵~~)。

你可能感兴趣的:(信息学,数据结构,lct,splay,模板,模版,splay,lct)