• 0XFF 前言&概念

Link-Cut Tree 是一种用来维护动态森林连通性的数据结构,适用于动态树问题。它采用类似树链剖分的轻重边路径剖分,把树边分为实边和虚边,并用 Splay 来维护每一条实路径。Link-Cut Tree 的基本操作复杂度为均摊O(logn),但常数因子较大,一般效率会低于树链剖分。但是却可以解决树链剖分解决不了的问题(或者优化码量) -----Menci dalao

动态树LCT(link cut tree)是一个可以动态维护森林上各种信息的东西(删除查找合并啥的都有吧),原来的森林我们称为原森林,里面有实边和虚边,为啥有这两种边呢,首先LCT是用很多个splay维护这个森林的信息,那么因为splay本来就是个二叉树,所以我们要将原森林”剖分”成很多个二叉树并且用splay来维护它,用实边连接起来的一棵树就是原森林中的一棵树,我们称它为原树。

这个Splay会有些特殊,它的关键字是节点在树里面的深度。

这棵原树我们也不是直接用splay维护,而是按每个点在原树中的深度为优先级,将每个点以优先级的中序遍历丢到splay上。我们一般将原树所对应的splay称为辅助树,原森林就对应一个辅助树森林。

-----quhengyi11 dalao

请务必先将上文读清楚,再继续下面的阅读。

Splay是辅助树,阅读时不要将主的和辅的搞混了。

显然原树中同一个深度的点是不可能在一个splay里的,因此每个splay里面就是维护了原树中的一条链

Link-Cut Tree 准确的说是一个 Splay 森林。每棵 Splay 都用"虚边"链接(下图灰边表示虚边),每棵 Splay 中的结点都用"实边"链接起来(下图用黑色表示实边)。假如我们现在有一个栗子:(用红色圈圈圈在一起的结点是一个 Splay 中的结点)

那么现在每个结点都是一颗 Splay

就像这样:

如果我们将1,2连接起来的话。

那么1,2就是同一个 Splay 中的节点了。

那么现在的情况就是这样:

相信你一定对此有些了解了吧。

  • 0X01 一些基本的定义

f[x]:结点x的爸爸(father)

v[x]:结点x的权值(value)

s[x]:结点x及它的子树的权值和(sum)

r[x]:结点x的翻转情况(rev)

ch[x][0/1]:结点x的左/右儿子

  • 0X02 一些操作

Link-Cut Tree 支持以下几种基本操作:

Access(x):将x到根节点的路径上全部变成实边,并弃掉自己所有的儿子(变成虚边:认父不认子)(每一个父结点对于自己的每个子结点只有一条实边)

findroot(x):找出x所在的原树的根结点(实际上就是上图的一号点)

makeroot(x):这个操作的意思是将x点变为原树的根节点

split(x,y):将x,y搞在一个 Splay 中,以方便操作。

link(x,y):将x和y所在原树合并起来(链接)

cut(x,y):将x和y所在原树拆开(切断)

  • Access(x):

这是最基础的操作,意思是将点x到原树中根结点root之间的链丢到一个辅助树splay里面

比方说,现在森林的状态是这样的:

我们的 x 现在等于6。执行 Access(6)

那么就会将{1-3,3-6}变成实边,1-2变成虚边,假设6有一儿子n,之间用实边连着,那么这条边也将变成虚边

每次将\(x\)点 splay 到当前所在辅助树的根节点,将它的右儿子更新为上一个\(x\),然后令\(x\)跳到它的父节点,特别的,第一个\(x\)的右儿子设为0(NULL)。

  1. Q:为什么是右儿子而不是左儿子呢?
  2. A:因为f[x]的深度小于x,而在Splay里面f[x]是x的爸爸,所以xSplay中是f[x]的右儿子。

所以就变成了这样:

我们将\(x\)旋转到辅助树的根节点,也就是将当前原树这条链上深度小于\(x\)(在\(x\)上面的点)丢到了\(x\)的左子树上,将\(x\)的右子树设为上一个\(x\)点相当于将\(x\)原来的右子树丢到了新的 splay 里面(而它们之间用虚边相连),并且将上一段链连接起来。

现在就可以了。这棵新 Splay 中只有这条链上的结点,没有其他任何的结点。如果我们指定要这三个结点同时进行操作,可以直接下传懒标记到这三个结点组成的 Splay 的根结点哦!到后面Splay的时候就可以直接下传跟新结点信息了。

总体过程:

虚边:儿子认父,父不认子

实边:儿子认父,父也认子

用FlashHu大佬的话来说,就是四步:

1.转到根。

2.换儿子。

3.更新信息。

4.当前操作点切换为轻边所指的父亲,转1。

代码实现:

  1. inline void Access(int x){
  2. for(register int y=0;x;y=x,x=f[x]){
  3. Splay(x);//转到所在Splay的根节点
  4. ch[x][1]=y;//认儿子了
  5. pushup(x);//儿子有变化,更新
  6. }
  7. }

  • findroot(x):

  • 首先要明白:

  • 根节点是的深度最小的

我们可以通过x向上找,用 Access 操作可以将x和x的根结点搞到一个 Splay 里。

又因为有BST的性质:x的左子树所有结点的权值 < x < x右子树所有结点的权值。

而我们又知道,在执行完 Access 操作后,这课 Splay 里面的结点权值最大的(深度最大的)就是x。

于是我们可以将x Splay 到这棵 Splay 的根结点,那么现在最左边的节点便是这课树的根结点了。

代码实现:

  1. inline int findroot(int x){
  2. Access(x);//Access将x和根结点搞到同一个Splay中
  3. Splay(x);//转到Splay的根结点
  4. while(ch[x][0])pushdown(x),x=ch[x][0];//不断的找左儿子&更新节点信息
  5. return x;//最左边的就是根结点了。
  6. }

  • makeroot(x):

将x到根结点的路径上的点全部翻转(即x变成了根节点)

具体操作是我们先将x点与原树中的根打通一条链,那么现在它们就在同一棵辅助树里面了,我们发现x一定是在它所在的辅助树的中序遍历的最后一个的(因为它是这条链上最深的点),我们把x点 splay 到辅助树的根上,那么x显然是没有右子树的,我们要实现将x移到原树的根上,也就是将x到根的这条链的深度全部翻转一遍,在辅助树上的体现就是将整棵树翻转一遍,我们可以写个翻转标记来减少复杂度。

代码实现:

  1. inline void filp(int x){//Splay普通区间翻转
  2. swap(ch[x][0],ch[x][1]);r[x]^=1;
  3. }
  4. inline void makeroot(int x){
  5. Access(x);
  6. Splay(x);
  7. filp(x);//懒标记&翻转区间
  8. }

  • split(x,y)

这个操作是将x到y之间的那条路径丢到一棵辅助树里,并且这棵辅助树以y节点为根(方便处理信息)。

Splay 维护的是原树中的一条链,我们不能保证x,y会在同一条链里。

所以我们可以先把x变成原树的根节点(这下子Access(y)就会将x到y之间的所有节点丢到一个 Splay 中了)。

最后如上面所讲的,最后来一个 Splay(y) 就大功告成了。

代码实现:

  1. inline void split(int x,int y){
  2. makeroot(x);Access(y);Splay(y);
  3. }

  • link(x,y):

将x和y所在原树合并起来(链接)

首先将x点丢到原树的根,然后去找找y的根是不是x,如果不是说明x,y不在一个原树内,我们将x的父节点设为y,也就相当于从y到x连了一条虚边。

代码实现:

  1. inline void link(int x,int y){
  2. makeroot(x);//丢到根
  3. if(findroot(y)!=x)f[x]=y;//链接一条虚边
  4. //注意因为是虚边,所以不能认儿子
  5. }

  • cut(x,y):

首先我们先把x,y之间的那条边用split(x,y)拎出来,因为x,y是相邻的,所以y的左儿子一定是x,将它们的父子关系消灭掉即可。

消灭父子关系时一定满足以下条件:

1.x和y在一个原树里(不在一个树里面往哪儿切啊)

2.split之后x是y的左儿子

3.x的右儿子是空的(保证了中序遍历中y紧跟在x的后面,即深度相邻)(x的权值(深度)只比y小1,而x又正好是直接连着y的,所以我们无法再找到 >x 而又 <y 的整数了)

代码实现:

  1. inline void cut(int x,int y){
  2. split(x,y);
  3. if(findroot(y)==x&&f[x]==y&&!ch[x][1]){//判断各种条件
  4. f[x]=ch[y][0]=0;//彻底切断关系
  5. pushup(y);//儿子变了,更新
  6. }return;
  7. }

  • 0X03 Splay的改动:

  • 旋转的改动:

这里需要注意一下,如果x的父亲节点的父亲节点y已经不在当前的这棵辅助树上,只需要连单向边(也就是虚边,认父不认子),否则正常连就行,这里要和普通的rotate区分开来。

做个对比:

现在的rotate(x):

  1. inline void rotate(int x){
  2. int y=f[x],z=f[y],k=chk(x),v=ch[x][!k];
  3. if(get(y))ch[z][chk(y)]=x;ch[x][!k]=y,ch[y][k]=v;
  4. if(v)f[v]=y;f[y]=x,f[x]=z;pushup(y),pushup(x);
  5. }

普通的rotate(x):

  1. inline void rotate(int x){
  2. int y=f[x],z=f[y],k=chk(x),v=ch[x][!k];
  3. ch[z][chk(y)]=x;ch[x][!k]=y,ch[y][k]=v;
  4. f[v]=y;f[y]=x,f[x]=z;pushup(y),pushup(x);
  5. }
  • Splay的改动

同样要注意一下只能Splay到辅助树的根节点,Splay之前需先下传一下这一条链上需操作的所有的点,用栈来完成即可,可以手写栈来减少常数。

  1. inline void Splay(int x){
  2. int y=x,top=0;hep[++top]=y;
  3. while(get(y))hep[++top]=y=f[y];
  4. while(top)pushdown(hep[top--]);
  5. while(get(x)){//基本普通的Splay
  6. y=f[x],top=f[y];
  7. if(get(y))
  8. rotate((ch[y][0]==x)^(ch[top][0]==y)?x:y);
  9. rotate(x);
  10. }pushup(x);return;
  11. }


  • 0X04 一些题目代码:

  • luogu P3690【模板】Link Cut Tree(动态树)(模板题)

就是上文讲的。

Code:

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define inf 0x3f3f3f3f
  4. #define RI register int
  5. #define A printf("A")
  6. #define C printf(" ")
  7. using namespace std;
  8. const int N=3e5+2;
  9. template <typename Tp> inline void IN(Tp &x){
  10. int f=1;x=0;char ch=getchar();
  11. while(ch<'0'||ch>'9')if(ch=='-')f=-1,ch=getchar();
  12. while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();x*=f;
  13. }int f[N],v[N],s[N],r[N],hep[N],ch[N][2];
  14. inline int get(int x){
  15. return ch[f[x]][0]==x||ch[f[x]][1]==x;
  16. }
  17. inline void pushup(int x){
  18. s[x]=s[ch[x][1]]^s[ch[x][0]]^v[x];
  19. }
  20. inline void filp(int x){
  21. swap(ch[x][0],ch[x][1]);r[x]^=1;
  22. }
  23. inline void pushdown(int x){
  24. if(!r[x])return;r[x]=0;
  25. if(ch[x][0])filp(ch[x][0]);
  26. if(ch[x][1])filp(ch[x][1]);
  27. }
  28. inline void rotate(int x){
  29. int y=f[x],z=f[y],k=ch[y][1]==x,v=ch[x][!k];
  30. if(get(y))ch[z][ch[z][1]==y]=x;ch[x][!k]=y,ch[y][k]=v;
  31. if(v)f[v]=y;f[y]=x,f[x]=z;pushup(y);
  32. }
  33. inline void Splay(int x){
  34. int y=x,top=0;hep[++top]=y;
  35. while(get(y))hep[++top]=y=f[y];
  36. while(top)pushdown(hep[top--]);
  37. while(get(x)){
  38. y=f[x],top=f[y];
  39. if(get(y))
  40. rotate((ch[y][0]==x)^(ch[top][0]==y)?x:y);
  41. rotate(x);
  42. }pushup(x);return;
  43. }
  44. inline void Access(int x){
  45. for(register int y=0;x;x=f[y=x])
  46. Splay(x),ch[x][1]=y,pushup(x);
  47. }
  48. inline void makeroot(int x){
  49. Access(x);Splay(x);filp(x);
  50. }
  51. inline int findroot(int x){
  52. Access(x);Splay(x);
  53. while(ch[x][0])pushdown(x),x=ch[x][0];
  54. return x;
  55. }
  56. inline void split(int x,int y){
  57. makeroot(x);Access(y);Splay(y);
  58. }
  59. inline void link(int x,int y){
  60. makeroot(x);if(findroot(y)!=x)f[x]=y;
  61. }
  62. inline void cut(int x,int y){
  63. makeroot(x);
  64. if(findroot(y)==x&&f[x]==y&&!ch[x][1]){
  65. f[x]=ch[y][0]=0;pushup(y);
  66. }return;
  67. }int n,m,x,y,op;
  68. int main(){
  69. scanf("%d%d",&n,&m);
  70. for(register int i=1;i<=n;++i)scanf("%d",&v[i]);
  71. for(register int i=1;i<=m;++i){
  72. scanf("%d%d%d",&op,&x,&y);
  73. if(op==0)split(x,y),printf("%d\n",s[y]);
  74. else if(op==1)link(x,y);
  75. else if(op==2)cut(x,y);
  76. else Splay(x),v[x]=y;
  77. }return 0;
  78. }

  • [SDOI2008]洞穴勘测

分析:题目只要求link(有一条新道路连接)和cut(道路被摧毁了cut)以及判断连通性(直接findroot,一样的话那么就是联通的)

就是LCT的板子,真的没那么难。

Code:

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define inf 0x3f3f3f3f
  4. #define RI register int
  5. #define A printf("A")
  6. #define C printf(" ")
  7. using namespace std;
  8. const int N=2e5+2;
  9. template <typename Tp> inline void IN(Tp &x){
  10. int f=1;x=0;char ch=getchar();
  11. while(ch<'0'||ch>'9')if(ch=='-')f=-1,ch=getchar();
  12. while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();x*=f;
  13. }int n,m,f[N],r[N],hep[N],ch[N][2];
  14. inline int get(int x){return ch[f[x]][0]==x||ch[f[x]][1]==x;}
  15. inline void filp(int x){swap(ch[x][0],ch[x][1]);r[x]^=1;}
  16. inline void pushdown(int x){
  17. if(!r[x])return;r[x]=0;
  18. if(ch[x][0])filp(ch[x][0]);
  19. if(ch[x][1])filp(ch[x][1]);
  20. }
  21. inline void rotate(int x){
  22. int y=f[x],z=f[y],k=ch[y][1]==x,v=ch[x][!k];
  23. if(get(y))ch[z][ch[z][1]==y]=x;ch[x][!k]=y,ch[y][k]=v;
  24. if(v)f[v]=y;f[y]=x,f[x]=z;return;
  25. }
  26. inline void Splay(int x){
  27. int y=x,top=0;hep[++top]=y;
  28. while(get(y))hep[++top]=y=f[y];
  29. while(top)pushdown(hep[top--]);
  30. while(get(x)){
  31. y=f[x],top=f[y];
  32. if(get(y))
  33. rotate((ch[y][0]==x)^(ch[top][0]==y)?x:y);
  34. rotate(x);
  35. }return;
  36. }
  37. inline void Access(int x){
  38. for(register int y=0;x;x=f[y=x])
  39. Splay(x),ch[x][1]=y;
  40. }
  41. inline void makeroot(int x){
  42. Access(x);Splay(x);filp(x);
  43. }
  44. inline int findroot(int x){
  45. Access(x);Splay(x);
  46. while(ch[x][0])pushdown(x),x=ch[x][0];
  47. return x;
  48. }
  49. inline void split(int x,int y){
  50. makeroot(x);Access(y);Splay(y);
  51. }
  52. inline void link(int x,int y){
  53. makeroot(x);if(findroot(y)!=x)f[x]=y;
  54. }
  55. inline void cut(int x,int y){
  56. makeroot(x);
  57. if(findroot(y)==x&&f[x]==y&&!ch[x][1]){
  58. f[x]=ch[y][0]=0;
  59. }return;
  60. }char op[16];
  61. int main(){
  62. scanf("%d%d",&n,&m);
  63. for(register int x,y,i=1;i<=m;++i){
  64. scanf("%s%d%d",op,&x,&y);
  65. if(op[0]=='C')link(x,y);
  66. else if(op[0]=='D')cut(x,y);
  67. else if(op[0]=='Q'){
  68. if(findroot(x)==findroot(y))printf("Yes\n");
  69. else printf("No\n");
  70. }
  71. }return 0;
  72. }

再推存一道题目:P1501 [国家集训队]Tree II

这道题目主要就是懒标记的运用,建议在做这一道题之前先去做一做线段树的模板2,其实道理差不多,相通的,并不难。(讲乘法标记的正确下传方法弄到Splay的下传上即可)

当然这道题我也附上题解:题解 P1501【[国家集训队]Tree II】(Link-Cut-Tree)


浅谈Link-Cut Tree(LCT)的更多相关文章

  1. 洛谷P3690 [模板] Link Cut Tree [LCT]

    题目传送门 Link Cut Tree 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代 ...

  2. BZOJ 3282 Link Cut Tree (LCT)

    题目大意:维护一个森林,支持边的断,连,修改某个点的权值,求树链所有点点权的异或和 洛谷P3690传送门 搞了一个下午终于明白了LCT的原理 #include <cstdio> #incl ...

  3. Luogu 3690 Link Cut Tree

    Luogu 3690 Link Cut Tree \(LCT\) 模板题.可以参考讲解和这份码风(个人认为)良好的代码. 注意用 \(set\) 来维护实际图中两点是否有直接连边,否则无脑 \(Lin ...

  4. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

  5. LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板

    P3690 [模板]Link Cut Tree (动态树) 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两 ...

  6. Link/cut Tree

    Link/cut Tree 一棵link/cut tree是一种用以表示一个森林,一个有根树集合的数据结构.它提供以下操作: 向森林中加入一棵只有一个点的树. 将一个点及其子树从其所在的树上断开. 将 ...

  7. P3690 【模板】Link Cut Tree (动态树)

    P3690 [模板]Link Cut Tree (动态树) 认父不认子的lct 注意:不 要 把 $fa[x]$和$nrt(x)$ 混 在 一 起 ! #include<cstdio> v ...

  8. Link Cut Tree学习笔记

    从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子 ...

  9. Link Cut Tree 总结

    Link-Cut-Tree Tags:数据结构 ##更好阅读体验:https://www.zybuluo.com/xzyxzy/note/1027479 一.概述 \(LCT\),动态树的一种,又可以 ...

  10. 【刷题】洛谷 P3690 【模板】Link Cut Tree (动态树)

    题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor ...

随机推荐

  1. [WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互

    1. 前言 WPF有一个灵活的UI框架,用户可以轻松地使用代码控制控件的外观.例设我需要一个控件在鼠标进入的时候背景变成蓝色,我可以用下面这段代码实现: protected override void ...

  2. 爱奇艺面试Python,竟然挂在第5轮……

    今天给大家分享我曾经在爱奇艺的面试,过程还是比较有意思的,可以给大家一些参考 聊骚阶段 嗲妹妹:你好,我是爱奇艺的HR,我们正在招聘运维开发岗位,请问您最近有在看工作机会吗? 我:(这声音也太酥了吧我 ...

  3. bzoj 5019: [Snoi2017]遗失的答案【dp+FWT】

    满足GL的组合一定包含GL每个质因数最大次幂个最小次幂,并且能做限制这些数不会超过600个 然后质因数最多8个,所以可以状压f[s1][s2]为选s1集合满足最大限制选s2集合满足最小限制 dfs一下 ...

  4. bzoj 3173: [Tjoi2013]最长上升子序列【dp+线段树】

    我也不知道为什么把题看成以插入点为结尾的最长生生子序列--还WA了好几次 先把这个序列最后的样子求出来,具体就是倒着做,用线段树维护点数,最开始所有点都是1,然后线段树上二分找到当前数的位置,把这个点 ...

  5. alternatives 命令学习

    最经在捣鼓Cloudera的cdh ,发现里面使用了alternatives命令,由于不懂这个命令,让我走了好多弯路. 现在mark一下 ubuntu 12.04 系统的命令为:update-alte ...

  6. 删除一个ppa

    https://itsfoss.com/how-to-remove-or-delete-ppas-quick-tip/ 总结如下: 1: 桌面删除,进入software & update,然后 ...

  7. Ascall码的故事

    没事发个ascall码表,二进制值得研究呦 sub al,30h; and al,00001111b ;字符ascall转数字or al,00110000b; sub al,32; and al,11 ...

  8. Jquery | 基础 | 慕课网 | (*选择器)

    原生JS var elements1 = document.getElementsByTagName('*'); JQ var elements2 = $("*"); <!D ...

  9. 正睿OI提高组十连测 day1 总结

    可能是最简单的一场比赛了吧,结果却打得这么差... T1是个找规律题,结果一开始愚蠢地找错了规律,然后又对拍,到1h多一点才过掉 然后看t2和t3,以为t2是个水题,t3也只要处理一下就好了,先写t2 ...

  10. Chef and Graph Queries CodeChef - GERALD07

    https://vjudge.net/problem/CodeChef-GERALD07 可以用莫队+带撤销并查集做 错误记录: 1.调试时数组开小了,忘了改大就交了 2.88行和91行少了备份num ...