• 树链剖分+LCT


    前言

    • 填了一个巨坑,然而还有很多巨坑要填
    • 本片主要内容为LCT+树链剖分

    引子

    • 有一类问题,要求在一个序列中做区间修改,区间查询
    • 可以用线段树解决这一类问题
    • 有另一类问题,要求在一个序列中做区间修改,区间查询,还要求插入删除,区间反转,区间循环移动等等坑爹的操作(维修数列)
    • 可以用splay来解决这一类问题
    • 现在我们要把这些操作拓展到树上

    树链剖分

    给定一棵形态不变的树,要求支持查询一条链,修改一条链

    0x00 基本思想

    • 序列(只有一条链)上的问题我们已经可以解决
    • 我们尝试把这个树拆成很多条链来解决
    • 一种有效的办法是随机剖分(期望log)
    • 更稳定的办法是轻重链剖分

    0x01 轻重链剖分

    • 定义一个节点的size是以这个节点为根的子树中的节点数
    • 每个节点与其size最大的儿子的连边定义为重边,其他为轻边
    • 重边构成的极大链被称为重链(应该都知道极大和最大的区别吧?)
    • 如果用线段树维护每条重链,那么对于每次查询,重链上的复杂度是 重链段数*log
    • 那么重链段数最多有多少呢?
    • 容易发现重链数=轻边数+1
    • 而且每经过一条轻边,子树大小都至少翻倍(类似于启发式合并)
    • 所以轻边数是log级别的
    • 那么重边数也是log级别的
    • 那么每次查询就是log^2级别的
    • 所以总复杂度是$ O(qlog^2{n}) $

    0x02 具体实现

    • 第一次dfs求出每个节点的size,depth,father,重儿子
    • 第二次dfs先走重儿子,求出每个节点的dfn和top(这个节点所在的重链顶端)
    • 查询(修改类似)的时候
    1. 两个节点的top不同,goto 2,否则goto 3
    2. 让深度大的一个节点跳到它top的father并且在答案中更新这一段,goto 1
    3. 在线段树上查询两个节点的中间一段,结束查询

    0x03 Extra

    • 如何维护链同时支持子树查询?
    • 发现虽然第二次dfs改变了dfs序,但是一个子树中的dfn还是连续的一段
    • 在每个节点中维护一下这个节点为根的子树中最大的dfn,修改比链还要简单

    0x04 模板题

    • 软件包管理器,真·模板题,兼顾子树查询和链查询

    LCT

    给定一棵形态变化的树,要求支持查询和修改一条链

    0x00 基本思想

    • 形态变化那只好splay啦
    • 还是维护一些链
    • 只不过因为形态会变化,不能轻重链剖分了
    • 运用splay的思想,splay把最近操作过的节点splay到根,在LCT中我们把最近操作过的节点到根的路径剖成一条链
    • 用splay维护一条链,这个splay叫做auxiliary tree

    0x01 一些定义

    • father:如果一个点不是所在splay的根,那么这个father指的是auxiliary tree中的father(即与普通splay中的father含义相同),如果这个点是所在splay的根,那么这个点的father指的是splay中深度最小的节点的真·父亲(即原树上的父节点)
    • child:splay中的孩子,不维护原树上的孩子
    • 偏爱儿子:最后一次被access过的儿子(在access路径上的儿子),特别的,如果access的节点是p,那么p没有偏爱儿子

    0x02 基本操作

    splay的操作

    • 要注意整个LCT实际上在实现的时候只用一棵splay维护,并不是很多很多splay
    • 注意LCT中的father和普通splay中的father有区别,在旋转的时候要注意改儿子的问题(一个节点可能不是他的father的child)

    access

    • 把一个节点p到根的路径变成偏爱路径
    • 首先p不能有偏爱儿子了,把p splay到根,断开右儿子
    • :loop
    • 把p的father splay到根,把p接到它father的右儿子上
    • 如果还没到根,p=p->father,goto loop

    make_root

    • 把p变为LCT的根
    • 异常简单
    • access(p)
    • splay(p)
    • 给p打一个翻转标记
    • 原理:实际上p到根的路径上深度都反过来了,其他部分树的形态并未改变,加个翻转标记即可
    • 在p,q两个节点间加一条边(保证仍是一个森林)
    • make_root(p)
    • p->father=q

    cut

    • 把p,q间的边断掉
    • make_root(p)
    • access(q)
    • splay(q)
    • 断掉q的左儿子即可

    链上修改

    • make_root(p)
    • access(q)
    • splay(q)
    • 对q像splay那样打标记就行了

    维护联通性

    • 暴力p=p->father,看最后是否相同

    0x03 假如我非要加入子树操作呢

    • 不好意思LCT不能同时支持子树修改和子树查询
    • 仅支持子树查询可以维护虚边信息
    • 仅支持子树修改非常难搞,我也不会写

    0x04 假如我非要维护边的信息呢

    • 懒办法:把一条边拆成两条边一个点,在点上维护信息

    0x05 模板题

    • 洞穴勘测
    • 弹飞绵羊

    总结

    • 比想象中的好写多了。。。
    许愿弥生改二
  • 相关阅读:
    Ubuntu 16.04 安装 Apache, MySQL, PHP7
    Ubuntu下apache2启动、停止、重启、配置
    织梦-数据库-表和字段说明手册
    DEDECMS去除后门隐患和漏洞以及冗余代码的方法
    Express使用html模板
    windows系统 安装MongoDB
    linux搭建node.js环境
    配置vuejs加载模拟数据
    安卓高级5 zXing
    安卓高级5 传感器和震动 模仿微信摇一摇Ui效果
  • 原文地址:https://www.cnblogs.com/LoveYayoi/p/6745541.html
Copyright © 2020-2023  润新知