zoukankan      html  css  js  c++  java
  • LCT——基础学习笔记

    https://blog.csdn.net/The_OIer/article/details/106680416

    https://www.cnblogs.com/flashhu/p/8324551.html

    https://oi-wiki.org/ds/lct/

    这一篇是基础内容,具体应用请看这篇

    前置芝士:Splay,文艺平衡树(即下放标记),树链剖分(思想最好会,不过不会也可以)。

    1 LCT可以用来解决什么问题

    LCT,Link-Cut-Tree。是一种用来解决动态树问题的数据结构。记住LCT不是动态树,只是用来解决动态树问题。

    基本操作:

    从x连向y一条边

    把x到y的边断开

    换根

    查询路径和(或其它Splay可以维护的东西)

    查询连通性相关

    ......

    LCT还有很多套路,后面的应用篇中会提到。

    2 实链剖分

    LCT维护的实际上是多棵树组成的森林。

    对于没课树我们进行实链剖分。

    这是一种新的将树剖成链的方法。

    类似重链剖分,实链剖分是把树剖成实链和虚链(注意实链和虚链都是树上的链)。

    不同的是,实链是我们自己赋予并且可以随意更改的,所以我们需要一种更加灵活的数据结构来维护——Splay。

    我们将一棵树剖成实链和虚链,每一条实链上的点都用一棵Splay来维护,关键字是节点的深度。

    和重链剖分类似,每个点最多属于一条实链。

    3 LCT的性质

    很多都已经在实链剖分中提到了。

    每一个Splay中序遍历后得到的节点的深度一定是单调递增的(兄弟节点也不能出现)。

    边分为实边和虚边,实链通过Splay来维护,而虚边通常是一棵Splay连向另一棵Splay。

    具体来讲就是将Splay的根的父亲设为这课Splay深度最小的父亲,但其父亲没有这个儿子(认父不认子)。

    放张图片好理解一些:

     

     

    4 LCT的Splay与普通Splay的不同地方

    1 根的判断

    这里不能用简单的fa=0来判断根,因为它有可能是Splay的根但是它的fa指向另一棵Splay。

    那我们可以利用认父不认子的性质来进行判断,就是它的父亲的两个儿子都不是它,那它一定是一棵Splay的根。

    代码就一行:

    bool is_root(int x) {return son[fa[x]][0] != x && son[fa[x][1] != x;}

    2 splay

    splay的时候要先把它到根的路径全部都pushdown,而pushdown时一定要从上往下。所以我额外写了一个update函数。

    另外lct的splay只需转到根。

    inline void update(int x) {
        if (!is_root(x)) update(fa[x]);
        push_down(x);
    }
    inline void splay(int x) {
        update(x);
        for (int f = fa[x]; !is_root(x); rotate(x), f = fa[x]) {
            if (!is_root(f)) rotate(get(f) == get(x) ? f : x);
        }
        push_up(x);
    }

    5 动态树的基本操作的实现

    1 access

    这算是lct最重要的函数了。

    意思是把根到x路径上的边都改为实边。

    举个例子:

    现在我们要access(N),首先要将N所在的splay深度最小的点也就是L大通其与父亲的虚边,这时为了维护lct的性质,我们需要将I到K的实边变为虚边。

    然后就变成了下图:

    接下来我们要打通H-I的虚边,同上。最后我们会得到下图:

    注意到N-O的边也变虚了,那是因为这样代码好写。

    具体的代码实现:

    void access(int x) {
        for (int s = 0; x; s = x, f = fa[x]) {
            splay(x);//先将x splay到当前的根 
            son[x][1] = s;//将x 的右儿子改为实边 
            push_up(x);//因为更改了son,所以要记得 pushup 
        }
    }

    2 make_root

    make_root就是把某个点设为这棵树的根。

    如果将现在从根往下的所有边都变得有向。

    将根到x的路径上的边取反即可得到以x为根的一棵树。

    所以类似文艺平衡树我们先把x到当前根的路径上的点变为实边,然后将这棵splay打上反转标记即可。

    void make_root(int x) {
        access(x);
        splay(x);
        pushr(x);
    }

    3 find_root

    find_root是找当前树的根。

    通常用来判断连通性。

    先将 x 到根的路径打通,然后将 x 旋到根。

    其实答案就是深度最小的点,直接找就行。

    记得要 pushdown。

    void find_root(int x) {
        access(x);
        splay(x);
        while (son[x][0]) push_down(x), x = son[x][0];
        splay(x);//为了保证复杂度 
        return x;
    }

    4 split

    拉出x->y的一条链

    其实有了上面几个函数也很好写。

    先将x变为根,打通y到根的路径,最后将x或y变为根方便后面。

    inline void split(int x, int y) {
        make_root(x);
        access(y);
        splay(y);
    }

    5 link

    加一条x-y的边

    将x变为根,判断y的根是不是x(即判断是否连通)。

    如果不连通,将x的父亲设为y即可(认父不认子)。

    inline void link(int x, int y) {
        make_root(x);
        if (find_root(y) != x)
            fa[x] = y;//认父不认子 
    }

    6 cut

    把原来x-y的边删掉。

    如果x-y有边。

    那么拉出x到y的链即split(x,y),x必然是y的左儿子,将y的左儿子和x的fa都设为0即可。

    那如果没边怎么判断呢?

    首先如果y的左儿子不是x则一定不连通,如果x有右儿子则也一定不连通。

    inline void cut(int x, int y) {
        split(x, y);
        if (son[y][0] == x && !son[x][1]) {
            son[y][0] = fa[x] = 0;
        }
    }

    到目前为止所有LCT的基础操作就讲完了,不过LCT绝对不止这些应用,具体可见应用篇。

    6 总结

    lct维护的是一个森林,每一棵树我们通过是链剖分的方法把树边分为实边和虚边,实链用一棵Splay来维护。

    通过一个核心的操作access来实现其它很多操作。

  • 相关阅读:
    第05组 Beta版本演示
    第05组 Beta冲刺(4/4)
    第05组 Beta冲刺(3/4)
    第05组 Beta冲刺(2/4)
    第05组 Beta冲刺(1/4)
    第05组 Alpha事后诸葛亮
    第05组 Alpha冲刺(4/4)
    第05组 Alpha冲刺(3/4)
    第05组 Alpha冲刺(2/4)
    300iq Contest 3 C. Cells Blocking
  • 原文地址:https://www.cnblogs.com/zcr-blog/p/13096650.html
Copyright © 2011-2022 走看看