zoukankan      html  css  js  c++  java
  • LinkCutTree学习笔记

    LinkCutTree 学习笔记


    参考来源

    https://www.zybuluo.com/xzyxzy/note/1027479

    https://www.cnblogs.com/zhoushuyu/p/8137553.html


    目的&作用

    树的动态加边/删边

    维护两点联通性, 树链信息, 生成树等

    概述

    应为无根树可以随意钦点树根, Splay 树也同样

    所以下面的算法都基于从根连出去的树链形成的 Splay 树, 以及换根, 等等操作

    然后实际上并不用建出原树, 所有的连边的关系都在 LCT 中

    详解

    Splay以什么为关键值?以什么为下标?

    以深度为关键值(不用具体存储, 用二叉查找树表示相对关系)

    以原树中编号为下标

    实边和虚边

    • 每一条由实边形成的树链由一个 Splay 维护
    • 每个点至多有一条连向其儿子的实边
    • 一个原树会被划分为多块 Splay

    父与子

    假设 ch[x][0/1] 代表在 Splay 中 x 节点的左右儿子, fa[x] 为父亲

    • 在原树中与 x 虚边连接的节点就不会出现在 Splay 树的父子中
    • 一个 Splay 的 rootfa 连向原树中该实链链顶的父亲(特例), 而父亲没有这个儿子(显然)

    isroot(x)

    判断 x 是否是它所在的 Splay 的根
    判据很简单, 就是 "Splay 上父亲的左右儿子都不是它", 刚才讲过了
    注意是左右儿子都不是, 因为以深度为下标, 而深度时常会改变

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

    这样就不记录 root

    正是因为 root 会连接两个不同的 Splay , 所以在(LCT)中要特别注意边界问题, 在rotate()splay()等中都会涉及

    access(x)

    将树根到 x 的路径串在一个 Splay 中, 并修改相关实边虚边

    由于 Splay 的根是连向其他 Splay 的出口, 而实链链顶的父亲((fa[root]))则是另一个 Splay 的入口, 我们主要关注这种节点, 还有 x

    • 发现将 rootfa[root] 改实边后, (fa[root])的实边就要断开, 而该实儿子是(ch[][1])(深度较大)
    • 切换虚实边只要 ch[fa[root]][1]=root 即可, 一步到位

    我们能保证 root 刚被包含在根到 x 的路径上, 即新的 Splay 分区中(见makeroot), (但是为了保证复杂度?), 一开始将 x splay到根, 之后则选择(fa[root]), 即 fa[x] splay到根
    还有, splay改变了树的形态, 而splay的路径上的点可以在rotate的时候pushup掉, 那么还剩下根刚好没有pushup, 单独做一下即可(pushup(fa[x])时会把 x 统计上去, 不用担心新实边上的上传)

    总结一下代码就是这样
    注意一开始 x 的右儿子会连接 0 , 就是把 x 和比 x 深的那一部分断开了, 分了一个 Splay 出去, 但并不影响
    然后做完之后 x 在最底下

    void access(int x)
    {
        for (int y = 0; x; y = x, x = fa[x])
            splay(x), ch[x][1] = y, pushup(x);
    }
    

    "access"好难打啊,不如直接留一个头尾

    基于access()的一些操作

    makeroot()

    原树换根,用处看下面一些函数就知道了

    access(x)

    发现现在已经把(root --> x)抓起来了,而 x 在最深处

    于是splay(x) + reverse(x)就可以了(想想为什么?)
    因为只需要这条链翻转, 而链上挂出去的小树不用

    void makeroot(int x)
    {
        access(x); splay(x);
        reverse(x);
    }
    

    findroot()

    找 splay 上的根

    看代码

    int findroot(int x)
    {
        access(x); splay(x);
        while (ch[x][0]) x = ch[x][0];
        splay(x);
        return x;
    }
    

    link(x, y)

    void link(int x, int y)
    {
        if (findroot(x) == findroot(y)) return ;
        makeroot(x);
        fa[x] = y; //x整棵树接到y下面
    }
    

    split(x, y)cut(x, y)

    可以随便换根的

    看代码,想想为什么?

    void split(int x, int y)
    {
        makeroot(x);
        access(y); splay(y);
    }
    /*判断
    void cut(int x, int y)
    {
        makeroot(x);
        if (findroot(y) != x || fa[y] != x) return ; // 想想为什么?
        split(x, y);
        fa[x] = ch[y][0] = 0;
        pushup(y);
    }
    */
    void cut(int x, int y)
        {
    	split(x, y);
    	fa[x] = ch[y][0] = 0;
    	pushup(y);
        }
    

    reverse(x)

    void reverse(int now)
        {
    	if (!now) return ;
    	swap(ch[now][0], ch[now][1]);
    	rev[now] ^= 1;
        }
    

    rotate()splay(x)

    splay的路径上从上到下先pushdown()

    void rotate(int x)
        {
    	int y = fa[x], z = fa[y];
    	int yx = (ch[y][1] == x), zy = (ch[z][1] == y);
    	if (!isroot(y)) ch[z][zy] = x;
    	fa[x] = z; 
    	fa[ch[x][yx ^ 1]] = y; ch[y][yx] = ch[x][yx ^ 1]; 
    	fa[y] = x; ch[x][yx ^ 1] = y;
    	pushup(y); pushup(x);
        }
        void splay(int x)
        {
    	if (!x) return ;
    	stk[top = 1] = x;
    	for (int p = x; !isroot(p); p = fa[p]) stk[++ top] = fa[p];
    	while (top) pushdown(stk[top --]);
    	while (!isroot(x))
    	{
    	    int y = fa[x], z = fa[y];
    	    int zy = (ch[z][1] == y), yx = (ch[y][1] == x);
    	    if (!isroot(y))
    		(zy == yx) ? rotate(y) : rotate(x);
    	    rotate(x);
    	}
        }
    

    后续见例题,基础代码见模板题

    • 带修改判断两点联通性
    • 带修改查询路径异或和

    例题

    BZOJ2049: Sdoi2008Cave 洞穴勘测

    树, 三种操作, 连边, 删边(保证存在), 查询连通性

    树, 四种操作, 查询路径上异或和, 连边(要求已联通则不连), 删边(不保证联通), 修改点权

    修改点权:把根换成他,这样单点修改他就行了,影响之后要用的时候自然会算到

    查询:split出来,完事

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    
    using namespace std;
    typedef long long LL;
    const int MAXN = 3e5 + 10;
    
    inline LL in()
    {
        LL x = 0, flag = 1; char ch = getchar();
        while (ch < '0' || ch > '9') { if (ch == '-') flag = -1; ch = getchar(); }
        while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
        return x * flag;
    }
    
    int n, m;
    
    int fa[MAXN], ch[MAXN][2], rev[MAXN];
    LL val[MAXN], sum[MAXN];
    int top, stk[MAXN];
    
    void reverse(int x)
    {
        if (!x) return;
        swap(ch[x][0], ch[x][1]);
        rev[x] ^= 1;
    }
    void pushup(int x)
    {
        sum[x] = sum[ch[x][0]] ^ sum[ch[x][1]] ^ val[x];
    }
    void pushdown(int x)
    {
        if (!rev[x]) return;
        reverse(ch[x][0]); reverse(ch[x][1]);
        rev[x] = 0;
    }
    bool isroot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; }
    void rotate(int x)
    {
        int y = fa[x], z = fa[y];
        int zy = (y == ch[z][1]), yx = (x == ch[y][1]);
        if (!isroot(y)) ch[z][zy] = x;
        fa[x] = z; // fa
        ch[y][yx] = ch[x][yx ^ 1], fa[ch[x][yx ^ 1]] = y;
        ch[x][yx ^ 1] = y; fa[y] = x;
        pushup(y); pushup(x); 
    }
    void splay(int x) // x --> root
    {
        if (!x) return ;
        stk[top = 1] = x;
        for (int tmp = x; !isroot(tmp); tmp = fa[tmp]) stk[++ top] = fa[tmp];
        while (top) pushdown(stk[top --]);
        while (!isroot(x))
        {
            int y = fa[x], z = fa[y];
            int zy = (y == ch[z][1]), yx = (x == ch[y][1]);
            if (!isroot(y))
                (zy == yx) ? rotate(y) : rotate(x);
            rotate(x);
        }
        pushup(x);
    }
    
    void access(int x)
    {
        for (int y = 0; x; y = x, x = fa[x])
            splay(x), ch[x][1] = y, pushup(x);
    }
    void makeroot(int x)
    {
        access(x); splay(x);
        reverse(x);
    }
    int findroot(int x)
    {
        access(x); splay(x);
        while (ch[x][0]) x = ch[x][0];
        splay(x);
        return x;
    }
    void link(int x, int y)
    {
        if (findroot(x) == findroot(y)) return ;
        makeroot(x);
        fa[x] = y;
    }
    void split(int x, int y)
    {
        makeroot(x);
        access(y); splay(y);
    }
    void cut(int x, int y)
    {
        makeroot(x);
        if (findroot(y) != x || fa[y] != x) return ;
        split(x, y);
        fa[x] = ch[y][0] = 0;
        pushup(y);
    }
    LL query(int x, int y)
    {
        split(x, y);
        return sum[y];
    }
    void modify(int x, LL y)
    {
        access(x); splay(x);
        val[x] = y;
        pushup(x);
    }
    
    int main()
    {
        n = in(), m = in();
        for (int i = 1; i <= n; ++ i) val[i] = in();
        while (m --)
        {
            int opt = in(), x = in(); LL y = in();
            if (opt == 0) printf("%lld
    ", query(x, y));
            else if (opt == 1) link(x, y);
            else if (opt == 2) cut(x, y);
            else if (opt == 3) modify(x, y);
        }
        return 0;
    }
    

    WC2006水管局长

    https://www.lydsy.com/JudgeOnline/problem.php?id=2594

    https://www.luogu.org/problemnew/show/P4172

    BZOJ3626: LNOI2014LCA

    BZOJ2002: [Hnoi2010]Bounce 弹飞绵羊

    裸题, 根据题意建立虚拟点, 删边加边,
    询问是原树中以某个点为根的某个点的深度
    可以先 makeroot 根, 然后 access, splay x , 直接用 siz 求得,
    也可你当成求路径长度

    再放一个板子

    
    namespace LCT
    {
        int ch[MAXN][2], fa[MAXN];
        int siz[MAXN];
        bool rev[MAXN];
        int stk[MAXN], top;
        void init(int x)
        {
    	siz[x] = 1;
    	ch[x][0] = ch[x][1] = fa[x] = 0;
        }
        bool isroot(int x)
        {
    	return ch[fa[x]][1] != x && ch[fa[x]][0] != x;
        }
        void pushup(int now)
        {
    	siz[now] = siz[ch[now][0]] + siz[ch[now][1]] + 1;
        }
        void reverse(int now)
        {
    	if (!now) return ;
    	swap(ch[now][0], ch[now][1]);
    	rev[now] ^= 1;
        }
        void pushdown(int now)
        {
    	if (!rev[now]) return ;
    	reverse(ch[now][0]); reverse(ch[now][1]);
    	rev[now] = 0;
        }
        void rotate(int x)
        {
    	int y = fa[x], z = fa[y];
    	int yx = (ch[y][1] == x), zy = (ch[z][1] == y);
    	if (!isroot(y)) ch[z][zy] = x;
    	fa[x] = z; 
    	fa[ch[x][yx ^ 1]] = y; ch[y][yx] = ch[x][yx ^ 1]; 
    	fa[y] = x; ch[x][yx ^ 1] = y;
    	pushup(y); pushup(x);
        }
        void splay(int x)
        {
    	if (!x) return ;
    	stk[top = 1] = x;
    	for (int p = x; !isroot(p); p = fa[p]) stk[++ top] = fa[p];
    	while (top) pushdown(stk[top --]);
    	while (!isroot(x))
    	{
    	    int y = fa[x], z = fa[y];
    	    int zy = (ch[z][1] == y), yx = (ch[y][1] == x);
    	    if (!isroot(y))
    		(zy == yx) ? rotate(y) : rotate(x);
    	    rotate(x);
    	}
        }
        void access(int x)
        {
    	for (int pre = 0; x != 0; pre = x, x = fa[x])
    	    splay(x), ch[x][1] = pre, pushup(x);
        }
        void makeroot(int x)
        {
    	access(x); splay(x); reverse(x);
        }
        int findroot(int x)
        {
    	access(x); splay(x);
    	while (ch[x][0]) x = ch[x][0];
    	splay(x);
    	return x;
        }
        void link(int x, int y)
        {
    	if (findroot(x) == findroot(y)) return ;
    	makeroot(x);
    	fa[x] = y;
        }
        void split(int x, int y)
        {
    	makeroot(x);
    	access(y); splay(y);
        }
        void cut(int x, int y)
        {
    	split(x, y);
    	fa[x] = ch[y][0] = 0;
    	pushup(y);
        }
        int getdep(int x)
        {
    	makeroot(n + 1); 
    	access(x);   
    	splay(x);  
    	return siz[ch[x][0]] + 1;
        }
    }
    
    int main()
    {
        n = in();
        for (int i = 1; i <= n + 1; ++ i)
        {
    	LCT::init(i);
        }
        for (int i = 1; i <= n; ++ i) 
        {
    	a[i] = in();
    	LCT::link(i, min(i + a[i], n + 1));
        }
        m = in();
        while (m --)
        {
    	int opt = in();
    	if (opt == 1)
    	{
    	    int x = in() + 1;
    	    printf("%d
    ", LCT::getdep(x) - 1);
    	}
    	else 
    	{
    	    int x = in() + 1, v = in();
    	    // (x --> x + a[x]) ==> (x --> x + v)
    	    LCT::cut(x, min(x + a[x], n + 1));
    	    LCT::link(x, min(x + v, n + 1));
    	    
    	    a[x] = v;
    	}
        }
        return 0;
    }
    
  • 相关阅读:
    CR开发笔记-1工作前的准备
    CR开发笔记-2基础飞机的搭建以及测试代码
    c++还有一个小时考试
    c# winform 打印笔记
    aspcms部署
    c#复习笔记 继承
    [转]IoC框架
    Cinder-2 窗口的创建过程
    Cinder-1 TinderBox
    admin模板
  • 原文地址:https://www.cnblogs.com/Kuonji/p/12060033.html
Copyright © 2011-2022 走看看