LCT动态树【史上最精简易懂的LCT讲解】
Link Cut Tree(动态树,LCT)
介绍
首先简单介绍一下Link Cut Tree,将一棵树分成轻边和重链,类似于树链剖分,但是树剖是静态的。LCT可以用于动态的加点和删点,甚至还可以换根。也就是LCT维护了一个动态的树。
LCT中的每棵Splay都维护了一条重链(实际上是实链,这里统一讲重链)上的答案。多颗Splay连成一棵树,这棵Splay满足左儿子在树上的深度(这里深度指的是在数上的深度(下同),不是Splay上的深度)小于根,右儿子大于根。所以这条Splay每个节点的深度一定是不同的,所以一棵Splay树存的节点是从上往下的一条路径。Splay的形状不是在树上节点的形状。(这段话都是重点,一定要牢记,否则后面很多代码理解不了)
Splay直接的连接方式是连父不连子的,也就是Fa[x]=y,Son[y]!=x。
实现方法
模板题
首先介绍一下变量:
int Top,que[MAXN];//用于Splay
int Son[MAXN][2];//(0/1)左/右儿子节点
int Fa[MAXN];//父节点
int W[MAXN];//权值
int Rtd[MAXN];//Lazy标记
接下来是Splay的基本操作:
int Get(int x){return x==Son[Fa[x]][1];}//判断是左儿子还是右儿子
void PushUp(int x){W[x]=W[Son[x][1]]^W[Son[x][0]]^a[x];}//更新当前节点的值
void PushDown(int x){//懒惰标记 int &L=Son[x][0],&R=Son[x][1];if(Rtd[x]){Rtd[L]^=1;Rtd[R]^=1;Rtd[x]^=1;swap(L,R);}
}
bool IsRoot(int x){return Son[Fa[x]][0]!=x&&Son[Fa[x]][1]!=x;}
//判断是否是Splay的根节点,因为Splay森林是连父不连子的,所以需要这么判。
void Rotate(int x){//旋转,将x变成y的父节点 int y=Fa[x],z=Fa[y],L,R;R=(L=Get(x))^1;if(!IsRoot(y)) Son[z][Son[z][1]==y]=x;//如果y不是根节点,那么将z的儿子y变成xFa[x]=z;Fa[y]=x;Fa[Son[x][R]]=y;Son[y][L]=Son[x][R];Son[x][R]=y;PushUp(y);PushUp(x);
}
void Splay(int x){//旋到Splay的根que[Top=1]=x;for(int i=x;!IsRoot(i);i=Fa[i]) que[++Top]=Fa[i];for(int i=Top;i;i--) PushDown(que[i]);//下传标记,一定要从上往下 while(!IsRoot(x)){//将x旋到根 int y=Fa[x],z=Fa[y];if(!IsRoot(y)) (Son[y][0]==x)^(Son[z][0]==y)?Rotate(x):Rotate(y);Rotate(x);}
}
然后重点来了:
Access(x):删除x到根路径上所有连向其他点的重链,重新拉一条x到根的重链。
实现也很简单,Splay到根,更新当前节点的儿子就可以了。
如果想知道Splay是如何实现这个,可以看https://www.cnblogs.com/flashhu/p/8324551.html,里面用图详细的用图像模拟了Access(x)的过程。
void Access(int x){for(int t=0;x;t=x,x=Fa[x])Splay(x),Son[x][1]=t,PushUp(x);}
解释一下为什么是Son[x][1]=tSon[x][1]=tSon[x][1]=t,因为我们要将新的链连上去,势必要删除原先深度大的链,否则就不是一条链了(建议再次看一下开头的重点)
MakeRoot(x):将x变成整棵树的根(不是Splay的根)。
所以要把x变成根就只需要让所有Splay的父亲最终指向x所在Splay。
所以先Access(x),Splay(x),把现在的根和将成为根的x链在一棵Splay中。
但是我们注意到,由于x成为了新的根,所以它和原来的根所在的Splay中深度作为关键字的性质遭到了破坏:新根x应该是Splay中深度最小的,但是之前的操作并不会改变x的深度(也就是目前x依旧是当前Splay中深度最深的)。
所以,我们需要把所在的这棵Splay翻转过来,x就变成了这整棵树深度最小的点(再次建议看一下重点)。
void MakeRoot(int x){Access(x);Splay(x);Rtd[x]^=1;}
Fnd_Root(x):找到x所在树的根节点(主要来判断连通性,可以看做并查集)。
我们知道深度最小的是根,那么一直不停的找左儿子就可以了
int Fnd_Root(int x){Access(x);Splay(x);while(Son[x][0]) x=Son[x][0];return x;}
Split(x,y):拉出一条x—y的路径成为一棵Splay。
先将MakeRoot(x),将x变成根,然后从y连接一条重链到根(也就是x),这样x和y就再同一棵Splay中了,然后将Splay(y)更新答案就可以了。
void Split(int x,int y){MakeRoot(x);Access(y);Splay(y);}
Cut(x,y):删除一条x到y的边
当然要分离出x,y然后进行操作,所以Split(x,y),然后当前的根是y,所以我们更新Fa[x]Fa[x]Fa[x]和Son[y][0]Son[y][0]Son[y][0],因为x一定是y的左儿子,否则表示x和y之间没有一条边,但是可以有路径。
void Cut(int x,int y){Split(x,y);if(Son[x][1]||Fa[x]!=y||Son[y][Get(x)^1]) return;Son[y][0]=Fa[x]=0;
}
Link(x,y):连接x和y
这个最好理解了,将x变成LCT的根,然后修改Fa[x]=yFa[x]=yFa[x]=y(因为Splay之间的连接是连父不连子的)。
void Lnk(int x,int y){MakeRoot(x);Fa[x]=y;}
比如说要得到x到y路径上的答案时,首先Split(x,y),将路径分离出来,当前Splay就是这条路径,根是y,所以直接输出y的值就可以了。
Split(x,y);
printf("%d\n",W[y]);
完整代码
#include<cstdio>
#include<cctype>
#include<algorithm>
#define MAXN 300005
using namespace std;
int n,m,a[MAXN];
struct Link_Cut_Tree{int Top,Son[MAXN][2],Fa[MAXN],W[MAXN],que[MAXN],Rtd[MAXN];int Get(int x){return x==Son[Fa[x]][1];}//判断是否是右儿子 void PushUp(int x){W[x]=W[Son[x][1]]^W[Son[x][0]]^a[x];}//更新当前节点的值 void PushDown(int x){//懒惰标记 int &L=Son[x][0],&R=Son[x][1];if(Rtd[x]){Rtd[L]^=1;Rtd[R]^=1;Rtd[x]^=1;swap(L,R);}}bool IsRoot(int x){return Son[Fa[x]][0]!=x&&Son[Fa[x]][1]!=x;}//判断是否是根节点 void Rotate(int x){//旋转,将x变成y的父节点 int y=Fa[x],z=Fa[y],L,R;R=(L=Get(x))^1;if(!IsRoot(y)) Son[z][Son[z][1]==y]=x;//如果y不是根节点,那么将z的儿子y变成xFa[x]=z;Fa[y]=x;Fa[Son[x][R]]=y;Son[y][L]=Son[x][R];Son[x][R]=y;PushUp(y);PushUp(x);}void Splay(int x){que[Top=1]=x;for(int i=x;!IsRoot(i);i=Fa[i]) que[++Top]=Fa[i];//一定要从上往下 for(int i=Top;i;i--) PushDown(que[i]);while(!IsRoot(x)){//将x旋到根 int y=Fa[x],z=Fa[y];if(!IsRoot(y)) (Son[y][0]==x)^(Son[z][0]==y)?Rotate(x):Rotate(y);Rotate(x);}}void Access(int x){for(int t=0;x;t=x,x=Fa[x]) Splay(x),Son[x][1]=t,PushUp(x);}//连接一条重链到根 void MakeRoot(int x){Access(x);Splay(x);Rtd[x]^=1;}//将x变成根 int Fnd(int x){Access(x);Splay(x);while(Son[x][0]) x=Son[x][0];return x;}//找x的根节点 void Split(int x,int y){MakeRoot(x);Access(y);Splay(y);}//拉出一条x到y的路径为一个Splay void Cut(int x,int y){//删除一条x到y的边 Split(x,y);if(Son[x][1]||Fa[x]!=y||Son[y][Get(x)^1]) return;Son[y][0]=Fa[x]=0;}void Lnk(int x,int y){MakeRoot(x);Fa[x]=y;}//连一条x到y的轻边
}Tre;
int read(){int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=getchar()) f^=!(ch^'-');for(; isdigit(ch);ch=getchar()) ret=(ret<<3)+(ret<<1)+ch-48;return f?ret:-ret;
}
int main(){#ifndef ONLINE_JUDGEfreopen("prob.in","r",stdin);freopen("prob.out","w",stdout);#endifscanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]),Tre.W[i]=a[i];while(m--){int opt=read(),x=read(),y=read();if(opt==0){Tre.Split(x,y);printf("%d\n",Tre.W[y]);}elseif(opt==1){if(Tre.Fnd(x)!=Tre.Fnd(y))Tre.Lnk(x,y);}elseif(opt==2){if(Tre.Fnd(x)==Tre.Fnd(y))Tre.Cut(x,y);}elseif(opt==3){a[x]=y;Tre.Access(x);Tre.Splay(x);Tre.PushUp(x);}}return 0;
}
转载于:https://www.cnblogs.com/XSamsara/p/10547924.html
LCT动态树【史上最精简易懂的LCT讲解】相关推荐
- 史上最精简Glide解析(一)
史上最精简Glide解析(一) 一.简介 Glide,一个被google所推荐的图片加载库,作者是bumptech.这个库被广泛运用在google的开源项目中,包括2014年的google I/O大会 ...
- Xshell下载安装,史上最简单易懂教程
Xshell下载安装,史上最简单易懂教程 阿里云网盘提取码:46gw 阿里云网盘下载地址 百度网盘提取码:8888 百度网盘下载地址 1,下载完成后,得到这样的文件 2,XmanagerPowerSu ...
- 深度梳理:史上最全 Python 字符串格式化讲解
大家好,今天给大家分享一篇堪称史上最全对字符串格式化的文章.喜欢点赞.收藏.关注. 上个周末看到"pandas数据格式化处理"的时候,想着把(设置小数位数,设置百分位,设置千位分隔 ...
- 【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树
目录 一.红黑树简介 二.为什么需要红黑树? 三.红黑树的特性 四.红黑树的效率 4.1 红黑树效率 4.2 红黑树和AVL树的比较 五.红黑树的等价变换 六.红黑树的操作 6.1 旋转操作 6.2 ...
- 人脸识别:史上最详细人脸识别adaface讲解-ckpt转onnx模型--第三节
这章节我会讲解的是我在工作上的项目,人脸识别adaface,以下的讲解为个人的看法,若有地方说错的我会第一时间纠正,如果觉得博主讲解的还可以的话点个赞,就是对我最大的鼓励~ 上一章节我们讲到了模型的训 ...
- [LCT动态树] [NOI2014]魔法森林,[ZJOI2018]历史
[NOI2014] 魔法森林 题目 按照aaa精灵从小到大排序 按顺序插入每一条边 加入第iii条边后的最小代价为a[i]a[i]a[i]加上从111到nnn的所有路径中最大bbb最小的路径代价 维护 ...
- 世界需要简化第五篇:阅读本文需要有基础,仅一文教你快速应用FOC的SVPWM实现三相异步电机调速?快速学会使用著名的电机SVPWM调速控制算法——史上最简单易懂,算法经高度抽象简化,所有下标经仔细核对
大家都知道,工业革命中所有机械的动力来源是电动机,对于直流电机的控制,可见我的博客.对拖动大负载场合的三相电机.如何快速把调速性能优越的SVPWM(空间电压矢量脉冲宽度调制)算法用出来,编程实现呢? ...
- 史上最直白易懂的五险一金介绍,初入职场的新人必知必懂!
目录 1.五险一金简单介绍 2.公积金(低利率买房贷款) 3.医疗保险(看病报销) 4.生育保险(生育报销和补助) 5.养老保险(退休领钱) 6.失业保险(失业补助) 7.工伤保险(工伤赔付) 1.五 ...
- 史上最详细易懂的ES6模块化语法(重点)
对于初次学习ES6的小伙伴来说,ES6的模块化语法是一个重点,在没有模块化之前,前端js代码有一下三句话 1.私密不漏 2.重名不怕 3.依赖不乱 一.接下来先演示在没有模块化之前,这个"私 ...
- 史上最简洁易懂的PGP邮件加密教程(MAC OS X版)
2019独角兽企业重金招聘Python工程师标准>>> 当我打算给我的Mac设置PGP加密时,我不知道这个到底有多难,我不仅要了解如何安装PGP软件,并且要去合理地运用起来.针对Ma ...
最新文章
- C语言第二次博客作业---分支结构
- P6134 [JSOI2015]最小表示(拓扑排序递推 + bitset优化,可达性统计变种)
- Linux内核之浅谈内存寻址
- IT民工——全世界最齐全的条形码库!包括Code128/Code93/Code39/EAN13等22种条形码
- 上云、微服务化和DevOps,少走弯路的办法
- opencv videocapture读取视频cap.isOpened 输出总是false
- 公司的Java框架应具备的通用功能
- 都快2022年了GraphQL还值得学吗?
- 存储过程返回结果集_PostgreSQL函数返回结果集
- 傅立叶变换,时域,频域二
- html5用本地存储做留言板,带有本地存储功能的留言板js代码
- matlab 计算峰均比,峰均比计算公式
- 规划控制下的二阶段设计理论 -【多核服务价值链协同】
- dubbo服务端线程池耗尽Server side threadpool is exhausted
- 【Python】matplotlib plt显示中文乱码解决方法
- Python——魔方方法
- 【微信群助手】微信社群怎么运营?微信群规则范本
- 如何做好网站页面的交互设计?
- Java 垃圾回收最全讲解(GC过程、可达性分析、方法,7大回收器)
- qq空间里每条说说的访客查询接口