「数据结构」Link-Cut Tree(LCT)

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475

#1.0 简述

#1.1 动态树问题

维护一个森林,支持删除某条边,加入某条边,并保证加边、删边之后仍然是森林。我们需要维护这个森林的一些信息。

一般的操作有两点连通性,两点路径权值和等等。

#1.2 实链剖分

先来回顾一下树链剖分,我们可以按照子树大小进行剖分(重链剖分),也可以按照子树高度进行剖分(长链剖分),使得原本的一棵树被分为若干条链,然后可以在链上通过如线段树这样的数据结构维护信息。

那么,存不存在一种剖分方式能够使我们更加得心应手地处理动态树问题?显然剖出的可能会不停变换,于是我们希望我们剖出来的链能够是我们想要的,那么只要我们选择剖出我们想要的链不就行了?(~ ̄▽ ̄)~

看起来很随意是不是?但是这就是实链剖分

  • 对于一个点连向儿子的所有边,我们自己选择一条作为实边,剩下的边作为虚边
  • 实边连向的儿子是实儿子,剩下的是虚儿子
  • 一条由实边组成的链,我们称之为实链

显然这种剖分方法是极为灵活的,灵活到一条实链上的点根本不是固定的 (lll¬ω¬),那么驯服这种放荡不羁的树链,我们要任用一种更加灵活的数据结构进行管理——Splay(伸展树)。

关于伸展树,可以参考笔者的博客 [数据结构]伸展树(Splay)

#1.3 辅助树

先来捋清各种名称之间的关系:

  • 树上的每个节点与 Splay 上的节点一一对应 ;
  • 一棵 Splay 维护一棵树上一条深度递增的链,Splay 上按照深度排序;
  • 若干颗 Splay 组成一棵辅助树(AuxTree),一棵辅助树代表一棵原树;
  • 所有辅助树组成 LCT;

一个很重要的点:辅助树上的所有 Splay 并不是相互独立的。原本一棵 Splay 的根节点是不应该有父节点的,但是在辅助树上,一棵 Splay 的根节点的父节点就是这棵 Splay 维护的树链在原树上的父亲。但是,这种父亲链接的特点是:子认父而父不认子,换句话讲,这种父亲链接中的父亲所存储的左右儿子都不是这个链接中的儿子,也就是说辅助树可能并不是一棵二叉树,同时,我们也可以利用这个性质快速判断一个节点是否是所在 Splay 的根节点。

同时,Splay 可以在满足辅助树、Splay 的性质的前提下任意变换。意味着原树中的根在辅助树中不一定是根。

如图,这是一棵原树:

其中实线代表的是实边,虚线代表是虚边。

那么相应的辅助树可能如下(Splay 是可以变换的哦 OvO):

#2.0 主要函数

一些变量及其含义

直接把代码丢在这里吧 ( ̄▽ ̄)

cpp

struct Node {
    int ch[2], fa; //左右儿子及父亲
    int rev\_tag, lzy\_tag; //翻转标记及信息懒标记
    int val, siz, var; //单点信息,子树大小,维护信息
} p[N];

#define f(x) p[x].fa
#define ls(x) p[x].ch[0]
#define rs(x) p[x].ch[1]

具体用途下文再说 <(ˉ^ˉ)>

一些基础操作

首先是 pushup()pushdown(),没什么好说的,根据题目有所不同。

cpp

inline void pushup(int k) {
    p[k].siz = p[p[k].ch[0]].siz + p[p[k].ch[1]] + 1;
    /*Other things that need to be maintained.*/
}

inline void pushdown(int k) {
    if (p[k].rev\_tag) {
        if (p[k].ch[0]) reverse(p[k].ch[0]);
        if (p[k].ch[1]) reverse(p[k].ch[1]);
        /*reverse() 是个什么函数请见下文*/
        p[k].rev\_tag = 0;
    }
    /*Other things that need to be maintained.*/
}

接下来是 get_type()isroot(),前者获取当前节点是父亲的哪一种儿子,后者则是判断当前节点是不是所在 Splay 的根。

get_type() 是 Splay 的经典操作了,不多展开;isroot() 需要用到辅助树上 Splay 的性质:根节点的父亲不认这个儿子,所以只需要判断当前节点的父亲记录的儿子中是否包含当前节点即可。

cpp

inline int get\_type(int x) {return x == p[p[x].fa].ch[1];}
inline int isroot(int x) {return x != ls(f(x)) && x
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值