【学习笔记】 - 基础数据结构 :Link-Cut Tree
发现树剖代码太长了,给我恶心坏了
学个代码短点的能写树剖题的数据结构吧
前置知识
树链剖分
简介以及优缺点介绍
Link-Cut Tree,也就是LCT,一般用于解决动态树问题
Link-Cut Tree可用于实现重链剖分的绝大多数问题,复杂度为\(O(n \log n)\),看起来比树剖的\(O(n \log^2 n)\)复杂度更小,但则不然,基于splay实现的Link-Cut Tree常数巨大(约11倍常数),往往表现不如树剖
Link-Cut Tree的代码往往比树剖少一些
动态树问题
维护一个森林,支持删除某条边,连接某条边,并保证加边/删边之后仍是森林
同时维护这个森林的一些信息
实链剖分
回顾重链剖分
按子树大小剖分整棵树并重新标号
此时树上形成了一些以链为单位的连续区间,用线段树进行区间操作
我们发现,诶重剖怎么是按子树大小来剖的,这也不能搞动态树啊
显然我们需要让剖分的链是我们指定的链,以便利用来求解
实链剖分
对于一个点连向它所有儿子的边,我们自己选择一条边进行剖分,我们称被选择的边为实边,其他边则为虚边。
我们称实边所连接的儿子为实儿子,实边组成的链称之为实链
选择实链剖分的最重要的原因便是因为实链是我们选择的,灵活且可变
正是它的这种灵活可变性,用
Splay来维护这些实链
Link-Cut Tree
我们可以把 LCT 理解为用一些 Splay 来维护动态树剖并实现动态树上的区间操作
每条实链都建一个 Splay 维护整个链的区间信息
辅助树
我们认为一些
Splay共同构成了一颗辅助树,每个辅助树都维护了一颗树,所有的辅助树构成了Link-Cut Tree,维护了整个森林辅助树有很多性质
辅助树由多棵
Splay组成,每棵Splay都维护了树中一条严格在原树中「从上到下」深度单调递增的路径,且中序遍历这棵Splay得到的点的深度序列单调递增原本的树的每个节点与辅助树的
Splay节点一一对应。辅助树各棵
Splay间并不独立。在LCT中每棵Splay的根节点的父亲节点指向原树中这条链的父亲节点(即链最顶端的点的父亲节点)。特殊的,这里的儿子认父亲,父亲却不认儿子,对应原树的一条 虚边
故每个连通块恰好有一个点的父亲节点为空
维护任何操作都不需要维护原树
辅助树可以在任何情况下拿出一个唯一的原树
只需维护辅助树即可
这是一颗原树
\(\gets\)
这是建出的辅助树
\(\gets\)
代码实现
这里只有 LCT 特有的几个操作
数组定义
fa[x] //x的父亲节点
son[x][2] //x的左右儿子
sz[x] //x的子树大小
rev[x] //x是否需要对儿子进行翻转
splay操作和正常
splay不同的是LCT的每次splay影响的所有点都必须是当前splay中的钱而且在
splay操作前必须把它的所有祖先全都pushdown,因为LCT不一定把哪个点应用splay操作代码
inline bool isroot(int x){
return ((son[fa[x]][0]==x)||(son[fa[x]][1]==x));
}
inline void splay(int x){
int y=x,z=0;
st[++z]=y;
while(isroot(y)){
st[++z]=y=fa[y];
}
while(z){
push_down(st[z--]);
}
while(isroot(x)){
y=fa[x],z=fa[y];
if(isroot(y))
rotate((son[y][0]==x)^(son[z][0]==y)?x:y);
rotate(x);
}
push_up(x);
}
access操作LCT最重要的操作,其他所有操作都要用到它含义是访问某节点,作用是对于访问的节点 \(x\) 打通一条从树根到 \(x\) 的实链
如果有其他实边与新的实链相连则改为轻边
可以理解为专门开辟一条从 \(x\) 到 \(root\) 的路径,用
splay来维护这条路径实现方法
先把 \(x\) 旋转到所在
Splay的根用 \(y\) 记录上一次的 \(x\) (初始化\(y=0\)),把 \(y\) 接到 \(x\) 的右儿子上
这样就把上一次的实链接到了当前实链下
它原来的右儿子(也就是
LCT树中在 \(x\) 下方的点)与它所有的边自然变成了虚边记得
pushup代码
inline void access(int x){
for(int y=0;x;x=fa[y=x])
splay(x),
rc=y,push_up(x);
}
换根操作
作用是把某个节点变成树根(这里的根指的是整颗
LCT的根)再加上
access操作就能方便的提取出LCT上两点之间距离提取\(u\)到\(v\)的路径只需要
toroot(u),access(v),然后\(v\)所在的Splay对应的链就是\(u\)到\(v\)的路径实现方法
先
access一下,这样 \(x\) 就一路打通到了根,然后再splay(x),由于x是这条实链最下面的点,所以 \(x\) 的splay的右儿子是空的,左儿子是它上面所有点因为
splay是支持区间翻转的,所以只要给x打个翻转标记就翻转到根了代码
inline void toroot(int x){
access(x);
splay(x);
reserve(x);
}
link操作作用是链接两个辅助树,对于
link(u,v),表示 \(u\) 所在的辅助树和 \(v\) 所在的辅助树实现方法
只需要先
toroot(u),然后记fa[u]=v就可以了,就是把一整颗辅助树连到另一个点上代码
inline void link(int x,int y){
toroot(x);
if(Find(y)!=x)
fa[x]=y;
}
cut操作这个操作作用是切断某条边
实现方法
先分离出 \(x\) 到 \(y\) 的这条链
我们假设切断的点一定是相邻的(不相邻的特判掉),然后把 \(y\) 的左儿子(也就是
LCT中 \(y\) 的父亲)与 \(y\) 的边断掉就好了代码
inline void split(int x,int y){
toroot(x);
access(y);
splay(y);
}
inline int Find(int x){
access(x);
splay(x);
while(lc)
push_down(x),x=lc;
splay(x);
return x;
}
inline void cut(int x,int y){
toroot(x);
if(Find(y)==x&&fa[y]==x&&!son[y][0]){
fa[y]=son[x][1]=0;
push_up(x);
}
}
完整代码
点击查看代码
#define lc son[x][0]
#define rc son[x][1]
int fa[N],son[N][2],val[N],ans[N],st[N];
bool rev[N];
inline bool isroot(int x){
return ((son[fa[x]][0]==x)||(son[fa[x]][1]==x));
}
inline void push_up(int x){
ans[x]=ans[lc]^ans[rc]^val[x];
}
inline void reserve(int x){
int t=lc;
lc=rc;rc=t;
rev[x]^=1;
}
inline void push_down(int x){
if(rev[x]){
if(lc)reserve(lc);
if(rc)reserve(rc);
rev[x]=0;
}
}
inline void rotate(int x){
int y=fa[x],z=fa[y],k=son[y][1]==x,w=son[x][!k];
if(isroot(y))
son[z][son[z][1]==y]=x;
son[x][!k]=y;
son[y][k]=w;
if(w)
fa[w]=y;
fa[y]=x;fa[x]=z;
push_up(y);
}
inline void splay(int x){
int y=x,z=0;
st[++z]=y;
while(isroot(y)){
st[++z]=y=fa[y];
}
while(z){
push_down(st[z--]);
}
while(isroot(x)){
y=fa[x],z=fa[y];
if(isroot(y))
rotate((son[y][0]==x)^(son[z][0]==y)?x:y);
rotate(x);
}
push_up(x);
}
inline void access(int x){
for(int y=0;x;x=fa[y=x])
splay(x),
rc=y,push_up(x);
}
inline void toroot(int x){
access(x);
splay(x);
reserve(x);
}
inline int Find(int x){
access(x);
splay(x);
while(lc)
push_down(x),x=lc;
splay(x);
return x;
}
inline void split(int x,int y){
toroot(x);
access(y);
splay(y);
}
inline void link(int x,int y){
toroot(x);
if(Find(y)!=x)
fa[x]=y;
}
inline void cut(int x,int y){
toroot(x);
if(Find(y)==x&&fa[y]==x&&!son[y][0]){
fa[y]=son[x][1]=0;
push_up(x);
}
}
signed main(){
int n,m;FastI>>n>>m;
for(int i=1;i<=n;++i)
FastI>>val[i];
while(m--){
int opt,x,y;
FastI>>opt>>x>>y;
if(opt==0){
split(x,y);
FastO<<ans[y]<<endl;
}
else if(opt==1){
link(x,y);
}
else if(opt==2){
cut(x,y);
}
else if(opt==3){
splay(x);
val[x]=y;
}
}
}
【学习笔记】 - 基础数据结构 :Link-Cut Tree的更多相关文章
- 【学习笔记】LCT link cut tree
大概就是供自己复习的吧 1. 细节讲解 安利两篇blog: Menci 非常好的讲解与题单 2.模板 把 $ rev $ 和 $ pushdown $ 的位置记清 #define lc son[x][ ...
- C学习笔记-基础数据结构与算法
数据结构 数据(data)是对客观事物符号表示,在计算机中是指所有能输入的计算机并被计算机程序处理的数据总称. 数据元素(data element)是数据的基本单位,在计算机中通常做为一个整体进行处理 ...
- Link Cut Tree学习笔记
从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子 ...
- 学习笔记:Link Cut Tree
模板题 原理 类似树链剖分对重儿子/长儿子剖分,Link Cut Tree 也做的是类似的链剖分. 每个节点选出 \(0 / 1\) 个儿子作为实儿子,剩下是虚儿子.对应的边是实边/虚边,虚实时可以进 ...
- LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)
为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...
- link cut tree 入门
鉴于最近写bzoj还有51nod都出现写不动的现象,决定学习一波厉害的算法/数据结构. link cut tree:研究popoqqq那个神ppt. bzoj1036:维护access操作就可以了. ...
- Link/cut Tree
Link/cut Tree 一棵link/cut tree是一种用以表示一个森林,一个有根树集合的数据结构.它提供以下操作: 向森林中加入一棵只有一个点的树. 将一个点及其子树从其所在的树上断开. 将 ...
- 数论算法 剩余系相关 学习笔记 (基础回顾,(ex)CRT,(ex)lucas,(ex)BSGS,原根与指标入门,高次剩余,Miller_Rabin+Pollard_Rho)
注:转载本文须标明出处. 原文链接https://www.cnblogs.com/zhouzhendong/p/Number-theory.html 数论算法 剩余系相关 学习笔记 (基础回顾,(ex ...
- Link Cut Tree 总结
Link-Cut-Tree Tags:数据结构 ##更好阅读体验:https://www.zybuluo.com/xzyxzy/note/1027479 一.概述 \(LCT\),动态树的一种,又可以 ...
- jQuery学习笔记 - 基础知识扫盲入门篇
jQuery学习笔记 - 基础知识扫盲入门篇 2013-06-16 18:42 by 全新时代, 11 阅读, 0 评论, 收藏, 编辑 1.为什么要使用jQuery? 提供了强大的功能函数解决浏览器 ...
随机推荐
- [转帖]win10下使用Rclone将OneDrive映射到本地磁盘教程(开机自动挂载)
win10下使用Rclone将OneDrive映射到本地磁盘教程(开机自动挂载) 下载rclone,winfsp和Git bash Rclone. Winfsp. 和 [Git bash](https ...
- [转帖]JVM——内存区域:运行时数据区域详解
https://www.jianshu.com/p/cded765cfd1b 关注:CodingTechWork,一起学习进步. 引言 我们经常会被问到一个问题是Java和C++有何区别?我们除了 ...
- [转帖]Day63_Kafka(一)
第一讲 Kafka基础操作 课程大纲 课程内容 学习效果 掌握目标 Kafka简介 消息队列 掌握 Kafka简介 Kafka分布式环境 Kafka操作 Kafka shell 掌握 Kafka ap ...
- 程序调试利器——GDB使用指南
作者:京东科技 孙晓军 # 1\. GDB介绍 GDB是GNU Debugger的简称,其作用是可以在程序运行时,检测程序正在做些什么.GDB程序自身是使用C和C++程序编写的,但可以支持除C和C++ ...
- 从零开始匹配vim(0)——vimscript 简介
通过之前一系列的文章,相信各位小伙伴应该已经对vim产生了浓厚的兴趣,可能不少小伙伴通过慢慢的使用变的跟我一样,离开vim就不会写代码了.如果你希望继续长时间使用vim,甚至将vim作为主要的代码编辑 ...
- c++基础之表达式
这次接着更新<c++ primer> 这本书的读书笔记,上一篇博文更新到了书中的第三章,本次将记录书中的第四章--表达式 左值与右值 在理解表达式之前需要先理解c++中左值和右值的概念. ...
- TienChin 活动管理-添加活动页面
后端 ActivityController.java @Resource private IChannelService iChannelService; /** * 获取渠道列表 * * @retu ...
- Paddlenlp之UIE分类模型【以情感倾向分析新闻分类为例】含智能标注方案)
相关文章: Paddlenlp之UIE模型实战实体抽取任务[打车数据.快递单] 项目连接:百度AIstudio直接fork我的项目就可以复现 Paddlenlp之UIE分类模型[以情感倾向分析新闻分类 ...
- 2.1 CE修改器:精确数值扫描
本关是CE修改器的第一关,用户需要通过 Cheat Engine 工具完成精确扫描值.在这个练习中,需要将一个特定的数值(健康值)改变为 1000.首先,要确保数值类型设置正确,默认的是2字节或4字节 ...
- centos6.5安装MongoDB4.4.23
前言 1.目前MongoDB最新稳定版本是:6.0.8 2.MongoDB 5+和6+版本已不支持centos6.2+系统,参考https://docs.mongoing.com/install-mo ...