LCT 学习笔记
LCT学习笔记
前言
自己定的学习计划看起来完不成了(两天没学东西,全在补题),决定赶快学点东西
于是就学LCT了
简介
Link/Cut Tree是一种数据结构,我们用它解决动态树问题
但是LCT不叫动态树,动态树是指一类问题(那么LCT的中文名是啥啊)
这是⼀个和 Splay ⼀样只需要写几 (yi) 个 (dui) 核心函数就能实现一切的数据结构
动态树问题
维护一个森林,支持删除某条边,加入某条边,并保证加边,删边之后仍是森林。我们要维护这个森林的信息。
一般操作有两点连通性,两点路径权值和,连接两点和切断某条边、修改信息等。
从树链剖分转向动态树问题
普通的树剖以子树大小作为划分条件
然而动态树问题需要什么样的剖分呢?
实链剖分
- 对于一个点连向儿子的边,我们自行选择一条边进行剖分
- 称被选择的边为实边,其他边为虚边,实边连向的儿子为实儿子
- 对于一条实边组成的链,成为实链
- 采用Splay来维护实链
LCT!
我们把每条实链建一个Splay来维护整个链区间上的信息
辅助树
- 每⼀个 Splay 维护的是一条路径, 并且在原树中所有节点深度严格递增, 并且, 中序遍历这棵 Splay 得到的点序列列的点深度严格递增。
- 每个节点包含且仅包含于一棵 Splay 中。
- ⼀棵 Splay 的根节点的 Father 指向它在辅助树中的父亲结点。但是它父亲结点的 ch 并没有指向这个点的。即父亲不不⼀定认儿子,而儿子一定能找到父亲
- 我们维护任何操作都不不需要维护原树, 辅助树可以在任何情况下拿出一个唯一的原树, 我们只需要维护辅助树即可。
- 你可以认为一些 Splay 构成了一个辅助树,每棵辅助树维护的是一棵树,一些辅助树构成了 LCT ,其维护的是整个森林。
Example:
现在有一棵树,其中粗边为实边,虚线边为虚边
那么辅助树的结构如下
原树和辅助树的结构关系
- 原树中的实链 : 在辅助树中节点都在一棵 Splay 中
- 原树中的虚链 : 在辅助树中, 子节点所在 Splay 的 Father 指向父节点, 但是父节点的两个儿子都不指向子节点。
- 注意: 原树的根 ≠辅助树的根。
- 原树的 Father 指向 ≠辅助树的 Father 指向。
- 辅助树是可以在满足辅助树、Splay 的性质下任意换根的。
- 虚实链变换可以轻松在辅助树上完成, 这也就是实现了动态维护树链剖分。
变量声明
ch[N][2]
左右⼉子f[N]
⽗亲指向sum[N]
路径权值和val[N]
点权tag[N]
翻转标记laz[N]
权值标记
函数声明
⼀般数据结构函数 (字面意思)
PushUp(x)
PushDown(x)
Splay 系函数
Get(x)
获取 x 是父亲的哪个⼉子。Splay(x)
通过和 Rotate 操作联动实现把 x 旋转到 当前 Splay 的根。Rotate(x)
将 x 向上旋转一层的操作。
新操作
IsRoot(x)
判断当前节点是否是所在 Splay 的根Access(x)
把从根到当前节点的所有点放在⼀条实链里, 使根到它成为一条实路径, 并且在同一棵 Splay 里里。Update(x)
在 Access 操作之后, 递归的从上到下Pushdown
更新信息。MakeRoot(x)
使 x 点成为整个辅助树的根。Link(x, y)
在 x, y 两点间连⼀一条边。Cut(x, y)
把 x, y 两点间边删掉。Find(x)
找到 x 所在的 Splay 的根节点编号。Fix(x, v)
修改 x 的点权为 v。Split(x, y)
提取出来 x, y 间的路路径, 方便便做区间操作
宏定义
#define ls ch[p][0]
#define rs ch[p][1]
函数讲解
pushup
inline void pushup(int p){
//更新节点信息
siz[p]=siz[ls]+siz[rs];
//...
}
pushdown
inline void pushdown(int p){
if(tag1[p]!=std_tag_1){
//do ls & do rs
tag1[p]=std_tag_1;
}
if(tag2[p]!=std_tag_2){
//...
}
//...
}
Rotate & Splay
#define Get(x) (ch[f[x]][1]==x)
inline void Rotate(int x){
int y=f[x],z=f[y],k=Get(x);
if(!isRoot(y)) ch[z][ch[z][1]==y]=x;
//上面这句话一定要写在前面
ch[y][k]=ch[x][!k],f[ch[y][k]]=y;
ch[x][!k]=y,f[y]=x,f[x]=z;
pushup(x),pushup(y);
}
inline void Splay(int x){
update(x);
for(int fa=f[x];!isRoot(x);Rotate(x)){
if(!isRoot(fa)) Rotate(Get(fa)==Get(x)?fa:x);
}
}
上面的还是Splay用的函数,看不懂请自学Splay
下面要开始LCT独有的函数啦
isRoot
//如果一个儿子既不是父亲的左儿子,也不是右儿子,那么他就是根
//原因:我们说过如果一个儿子不是实儿子,那么父亲的ch里找不到他
#define isRoot(x) (ch[f[x]][0]!=x && ch[f[x]][1]!=x)
Access
//LCT的核心操作
inline void Aceess(int x){
for(int p=0;x;p=x,x=f[x]){
Splay(x),ch[x][1]=p,pushup(x);
}
}
我们有这样一棵树,实线为实边,虚线为虚边
- 它的辅助树可能长成这样 (构图方式不同可能 LCT 的结构也不同)
- 每个绿框里是一棵 Splay。
- 现在我们要 Access(N), 把 A-N 的路径都变实, 拉成一棵 Splay
- 实现的方法是从下到上逐步更新 Splay
- 首先我们要把 N 旋至当前 Splay 的根。
- 为了保证辅助树的性质, 原来 N——O 的实边要更改为虚边。
- 由于认父不认子的性质, 我们可以单方面的把 N 的儿子改为 Null。
- 于是原来的辅助树就变成了下图。
- 下一步, 我们把 N 指向的 Father-> I 也旋转到它 (I) 的 Splay 树根。
- 原来的实边 I —— K 要去掉, 这时候我们把 I 的右儿子指向 N, 就得到了 I——L 这样一棵 Splay。(如上图)
- 接下来, 按照刚刚的操作步骤, 由于 I 的 Father 指向 H, 我们把 H 旋转到他所在 Splay Tree 的根, 然后把 H 的 rs 设为 I。
- 之后的树是这样的。
- 同理我们 Splay(A) , 并把 A 的右儿子指向 H。
- 于是我们得到了这样一棵辅助树。并且发现 A——N 的整个路径已经在同一棵 Splay 中了。大功告成!
//回顾代码
inline void Access(int x){
for(int p=0;x;p=x,x=f[x]){
Splay(x),ch[x][1]=p,pushup(x);
}
}
我们发现 Access 其实很容易。只有如下四步操作:
- 把当前节点转到根。
- 把当前节点的右儿子换成之前的节点。
- 更新当前点的信息。
- 把当前点换成当前点的父亲, 继续操作。
update
//从上到下一层层pushdown即可
void update(int p){
if(!isRoot(p)) update(f[p]);
pushdown(p);
}
makeRoot
- makeRoot 的重要性丝毫不亚于 Access 。 我们在需要维护路径信息的时候, 一定会出现路径深度无法严格递增的情况, 根据辅助树的性质, 这种路径是不能出现在一棵 Splay 中的
- 这时候我们需要用到 Make_Root
- Make_Root 的作用是使指定的点成为原树的根
- 考虑如何实现这种操作
- 我们发现 Access(x) 后,x 在 Splay 中一定是深度最大的点 (从根到 x, 深度严格递增)
- 而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树 (即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下,变成了 x 没有左子树,x就是深度最小的点了,即达到目的
- 所以我们交换左右儿子,并给右儿子打一个翻转标记即可。(此时左儿子没有值)
inline void makeRoot(int p){
Access(p),Splay(p);
swap(ls,rs);tag[p]^=1;
}
Link
Link 两个点其实很简单, 先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然, 这个操作肯定不能发生在同一棵树内。记得先判一下。
inline void Link(int x,int p){
makeRoot(x);f[x]=p;
}
split
- Split 操作意义很简单, 就是拿出一棵 Splay , 维护的是 x 到 y 的路径。
- 先 MakeRoot(x) , 然后 Access(y) 。如果要 y 做根, 再 Splay(y) 。
- 就这三句话, 没写代码, 需要的时候可以直接打这三个就好辣!
- 另外 Split 这三个操作直接可以把需要的路径拿出到 y 的子树上, 那不是随便干嘛咯。
Cut
- Cut 有两种情况, 保证合法和不一定保证合法。(废话)
- 如果保证合法, 直接 split(x, y) , 这时候 y 是根, x 一定是它的儿子, 双向断开即可 , 就像这样:
inline void Cut(int x,int p){
makeRoot(x),Access(p),Splay(p),ls=f[x]=0;
}
- 如果是不保证合法, 我们需要判断一下是否有, 我选择使用 Map 存一下, 但是这里有一个利用性质的方法:
- 想要删边, 必须要满足如下三个条件:
- x, y 连通。
- x, y 的路径上没有其他的链。
- x 没有右儿子。
- 总结一下, 上面三句话的意思就一个:x, y 有边。
- 想要删边, 必须要满足如下三个条件:
Find
- Find() 其实就是找到当前辅助树的根。在 Access(p) 后, 再 splay(p)。这样根就是树里最小的那个, 一直往 ls 走, 沿途 PushDown 即可。
- 一直走到没有 ls, 非常简单。
inline int Find(int p) {
Access(p), Splay(p);
while(ls) pushdown(p), p = ls;
return p;
}
一些提醒
- 干点啥一定要想一想需不需要 pushup或者 pushdown, LCT 由于特别灵活的原因, 少 pushup 或者 pushdown 一次就可能把修改改到不该改的点上!
- 它的 rotate 和 Splay 的不太一样, if(z) 一定要放在前面。
- 它的 splay 就是旋转到根, 没有旋转到谁儿子的操作, 因为不需要。
一些题
- BZOJ_2049
- BZOJ_3282
- BZOJ_2002
- BZOJ_2631
LCT 学习笔记的更多相关文章
- LCT学习笔记
最近自学了一下LCT(Link-Cut-Tree),参考了Saramanda及Yang_Zhe等众多大神的论文博客,对LCT有了一个初步的认识,LCT是一种动态树,可以处理动态问题的算法.对于树分治中 ...
- [总结] LCT学习笔记
\(emmm\)学\(lct\)有几天了,大概整理一下这东西的题单吧 (部分参考flashhu的博客) 基础操作 [洛谷P1501Tree II] 题意 给定一棵树,要求支持 链加,删边加边,链乘,询 ...
- SPLAY,LCT学习笔记(六)
这应该暂时是个终结篇了... 最后在这里讨论LCT的一个常用操作:维护虚子树信息 这也是一个常用操作 下面我们看一下如何来维护 以下内容转自https://blog.csdn.net/neither_ ...
- SPLAY,LCT学习笔记(五)
这一篇重点探讨LCT的应用 例:bzoj 2631 tree2(国家集训队) LCT模板操作之一,利用SPLAY可以进行区间操作这一性质对维护懒惰标记,注意标记下传顺序和如何下传 #include & ...
- SPLAY,LCT学习笔记(四)
前三篇好像变成了SPLAY专题... 这一篇正式开始LCT! 其实LCT就是基于SPLAY的伸展操作维护树(森林)连通性的一个数据结构 核心操作有很多,我们以一道题为例: 例:bzoj 2049 洞穴 ...
- SPLAY,LCT学习笔记(一)
写了两周数据结构,感觉要死掉了,赶紧总结一下,要不都没学明白. SPLAY专题: 例:NOI2005 维修数列 典型的SPLAY问题,而且综合了SPLAY常见的所有操作,特别适合新手入门学习(比如我这 ...
- 【动态树问题】LCT学习笔记
我居然还不会LCT QAQ真是太弱了 必须学LCT QAQ ------------------线割分是我www------------ LinkCut-Tree是基于Splay(由于Splay能够非 ...
- SPLAY,LCT学习笔记(三)
前两篇讲述了SPLAY模板操作,这一篇稍微介绍一下SPLAY的实际应用 (其实只有一道题,因为本蒟蒻就写了这一个) 例:bzoj 1014火星人prefix 由于本蒟蒻不会后缀数组,所以题目中给的提示 ...
- SPLAY,LCT学习笔记(二)
能够看到,上一篇的代码中有一段叫做find我没有提到,感觉起来也没有什么用,那么他的存在意义是什么呢? 接下来我们来填一下这个坑 回到我们的主题:NOI 2005维修数列 我们刚刚讨论了区间翻转的操作 ...
随机推荐
- Ace(二)Demo示例
Client: #include "ace/Log_Msg.h" #include "ace/OS.h" #include "ace/Service_ ...
- TortoiseSVN使用笔记
TortoiseSVN版本冲突详解 下列步骤展示了如何将分支A中的修改合并到分支B. 1.在分支B的本地副本目录中选择"合并(Merge)". 2.选择“合并一个版本范围(Me ...
- Spring Boot缓存源码分析
前言 项目里面要增加一个应用缓存,原本想着要怎么怎么来整合ehcache和springboot,做好准备配置这个配置那个,结果只需要做三件事: pom依赖 写好一个ehcache的配置文件 在boot ...
- UVa 11572 唯一的雪花(优化策略)
题目描述: 输入一个长度为n(n<=1000000)的序列A, 找到一个尽量长的连续子序列A(L)-->A(R),是的该序列中没有相同的元素. 输入: T:代表组数 n:代表有n个数 这一 ...
- MySQL学习笔记(四)——分组数据group by子句和having子句
分组允许把数据分为多个逻辑组,以便对每个组进行聚集计算. 例如我们查下每个系里有多少名学生: 在使用group by 子句之前,还需要知道一些规定: 1. group by 子句可以包含任意数目的列, ...
- 在jboss中部署可执行jar, deploy executable jar in jboss
首先,题目是个伪命题, jboss容器是不支持直接部署可执行jar包的,jar只会被加载当作lib对待.这里提供了一个小的变通方案. 今天我遇到个问题,把我们的项目中的监控模块独立成一个小项目部署,监 ...
- 报错:'Navigator is deprecated and has been removed from this package. It can now be installed
报错:'Navigator is deprecated and has been removed from this package. It can now be installed ' + ...
- LoadRunner监控Linux与Windows方法
1.首先保证被监视的windows系统开启以下二个服务Remote Procedure Call(RPC) 和Remote Registry Service: 2.被监视的WINDOWS机器:右击我的 ...
- html5--4-2 video元素的属性
html5--4-2 video元素的属性 学习要点 掌握video元素的基本用法 直到现在,在网页中的大多数视频是通过插件(比如 Flash)来显示的.然而,并非所有浏览器都拥有同样的插件.HTML ...
- Android Studio中的“favorites”和“bookmark”
做项目难免来回查看某个文件的某个方法,某些文件可能访问率很高, 为了加快开发效率楼主推荐使用favorites (文件)bookmark (代码 行). favorites 的添加就在文件单击右键ad ...