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讲解】相关推荐

  1. 史上最精简Glide解析(一)

    史上最精简Glide解析(一) 一.简介 Glide,一个被google所推荐的图片加载库,作者是bumptech.这个库被广泛运用在google的开源项目中,包括2014年的google I/O大会 ...

  2. Xshell下载安装,史上最简单易懂教程

    Xshell下载安装,史上最简单易懂教程 阿里云网盘提取码:46gw 阿里云网盘下载地址 百度网盘提取码:8888 百度网盘下载地址 1,下载完成后,得到这样的文件 2,XmanagerPowerSu ...

  3. 深度梳理:史上最全 Python 字符串格式化讲解

    大家好,今天给大家分享一篇堪称史上最全对字符串格式化的文章.喜欢点赞.收藏.关注. 上个周末看到"pandas数据格式化处理"的时候,想着把(设置小数位数,设置百分位,设置千位分隔 ...

  4. 【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树

    目录 一.红黑树简介 二.为什么需要红黑树? 三.红黑树的特性 四.红黑树的效率 4.1 红黑树效率 4.2 红黑树和AVL树的比较 五.红黑树的等价变换 六.红黑树的操作 6.1 旋转操作 6.2 ...

  5. 人脸识别:史上最详细人脸识别adaface讲解-ckpt转onnx模型--第三节

    这章节我会讲解的是我在工作上的项目,人脸识别adaface,以下的讲解为个人的看法,若有地方说错的我会第一时间纠正,如果觉得博主讲解的还可以的话点个赞,就是对我最大的鼓励~ 上一章节我们讲到了模型的训 ...

  6. [LCT动态树] [NOI2014]魔法森林,[ZJOI2018]历史

    [NOI2014] 魔法森林 题目 按照aaa精灵从小到大排序 按顺序插入每一条边 加入第iii条边后的最小代价为a[i]a[i]a[i]加上从111到nnn的所有路径中最大bbb最小的路径代价 维护 ...

  7. 世界需要简化第五篇:阅读本文需要有基础,仅一文教你快速应用FOC的SVPWM实现三相异步电机调速?快速学会使用著名的电机SVPWM调速控制算法——史上最简单易懂,算法经高度抽象简化,所有下标经仔细核对

    大家都知道,工业革命中所有机械的动力来源是电动机,对于直流电机的控制,可见我的博客.对拖动大负载场合的三相电机.如何快速把调速性能优越的SVPWM(空间电压矢量脉冲宽度调制)算法用出来,编程实现呢? ...

  8. 史上最直白易懂的五险一金介绍,初入职场的新人必知必懂!

    目录 1.五险一金简单介绍 2.公积金(低利率买房贷款) 3.医疗保险(看病报销) 4.生育保险(生育报销和补助) 5.养老保险(退休领钱) 6.失业保险(失业补助) 7.工伤保险(工伤赔付) 1.五 ...

  9. 史上最详细易懂的ES6模块化语法(重点)

    对于初次学习ES6的小伙伴来说,ES6的模块化语法是一个重点,在没有模块化之前,前端js代码有一下三句话 1.私密不漏 2.重名不怕 3.依赖不乱 一.接下来先演示在没有模块化之前,这个"私 ...

  10. 史上最简洁易懂的PGP邮件加密教程(MAC OS X版)

    2019独角兽企业重金招聘Python工程师标准>>> 当我打算给我的Mac设置PGP加密时,我不知道这个到底有多难,我不仅要了解如何安装PGP软件,并且要去合理地运用起来.针对Ma ...

最新文章

  1. C语言第二次博客作业---分支结构
  2. P6134 [JSOI2015]最小表示(拓扑排序递推 + bitset优化,可达性统计变种)
  3. Linux内核之浅谈内存寻址
  4. IT民工——全世界最齐全的条形码库!包括Code128/Code93/Code39/EAN13等22种条形码
  5. 上云、微服务化和DevOps,少走弯路的办法
  6. opencv videocapture读取视频cap.isOpened 输出总是false
  7. 公司的Java框架应具备的通用功能
  8. 都快2022年了GraphQL还值得学吗?
  9. 存储过程返回结果集_PostgreSQL函数返回结果集
  10. 傅立叶变换,时域,频域二
  11. html5用本地存储做留言板,带有本地存储功能的留言板js代码
  12. matlab 计算峰均比,峰均比计算公式
  13. 规划控制下的二阶段设计理论 -【多核服务价值链协同】
  14. dubbo服务端线程池耗尽Server side threadpool is exhausted
  15. 【Python】matplotlib plt显示中文乱码解决方法
  16. Python——魔方方法
  17. 【微信群助手】微信社群怎么运营?微信群规则范本
  18. 如何做好网站页面的交互设计?
  19. Java 垃圾回收最全讲解(GC过程、可达性分析、方法,7大回收器)
  20. qq空间里每条说说的访客查询接口

热门文章

  1. 如何通过ip访问服务器文件共享,通过ip访问云服务器文件共享
  2. 用计算机用语说唯美的话,好听唯美的说说句子
  3. arcgis热点分析_地理信息系统导论学习笔记(11)——矢量数据分析
  4. 大数据导论章节答案_大数据概论智慧树章节答案
  5. bootstrap 文字不换行
  6. java用链表 编写记事本_(超详细) 动手编写 — 链表 (Java实现)
  7. python爬虫之多线程、多进程爬虫_python 多线程,多进程,高效爬虫
  8. 基于SSM的小说阅读网站
  9. Leetcode之插入区间
  10. 算数基本定理 + 例题