最近在复习学过的省选算法,发现以前学的太不扎实了,太逊了,有必要做笔记整理一下

LCT 笔记

主要功能

在线维护树的加边和删边,以及树相关信息

复杂度只有一个 \(\log\),乘以维护信息的复杂度(一般是 \(O(1)\))

和其它数据结构的比较

逊的:

  • 并查集+线段树分治:只能离线,而且不好维护树上的信息,倾向于维护联通块
  • 轻重链剖分:缺少加边和删边的功能,而且多一个 \(\log\) 的复杂度
  • 动态插入倍增:逊,只能加入一个点
  • 树分块:难以实现加边和删边(也可能是我不会),而且复杂度是根号的

老大哥:

  • top tree ,yyds (我不会qaq

思想

考虑对轻重链剖分进行魔改

轻重剖分是怎么剖的?一开始预处理出 \(size\),钦点 \(size\) 最大的是重儿子。

那么如果要支持加边和删边,每次重构显然不现实。

于是想到,也许不一定非要找最大的,方便修改就行了。

虚实剖分

这是轻重剖分的动态升级版,资瓷动态的钦点,哪些边是“实”的,哪些边是“虚”的。每个点只能有一个实儿子。然后实边连起来变成实链,将树划分成若干条链。

LCT全部都是基于虚实剖分,动态钦点重儿子,来实现链操作的。

如何维护所有的链

实链

考虑一下加边和断边的时候,如果把实链看成区间,其实就是在做区间合并和分裂操作,以及对一块区间做操作或者询问(链修改/询问)。那么什么结构可以快速的维护这东西呢?

Splay

于是维护方式为:用若干颗 \(Splay\) 维护若干个实链,每一颗 \(Splay\) 树的中序遍历,就对应一条实链从上往下的遍历。

举个栗子

这张图中,红色是实边,蓝色是虚边。

图中,\(A,B,D,H\) 在一个 Splay 里,\(E,I\) 在一个 Splay 里,\(C,G\) 在一个 Splay 里,\(F,J\) 两点都是单独一个点作为一个 Splay 的。

虚边

现在实边维护出来了,还有一个问题:虚边我也要记下来啊

既然要记下来,怎么和原来的实边区分开来?

有一个巧妙的实现,认父不认子 —— 我可能是你父亲,但你不一定是我儿子。

对于点 \(u\),如果 \(u\) 既不是 \(fa_u\) 的左儿子,也不是右儿子,那么 \((u,fa_u)\) 这条边就是虚边了。

开始构思

我们站在 Tar 老师的角度,考虑这个算法怎么实现。

首先是功能:支持加边,删边,链修改/询问。具体怎么做呢?

你可能会想,加边不是直接合并一下两个 Splay 就完了。删边也是,直接断一下即可。

但这会破坏现在剖好的链,而且无法维护。

考虑把根换成 \(u\),这样倒是可以直接操作。那我们就要支持一个换根操作了。

那么如何换根呢?

考虑把树想象成有向的,每条边从父亲连向儿子。那只要把 \(u\) 到根的路径边都反向一下,那 \(u\) 就是根了。由于我们用的是 Splay 维护,反向还是挺好搞的,就交换一下左右儿子即可。这个可以用 lazytag​ 实现。

那现在又要提取到根的路径了。这便是 LCT 的基础操作: access

具体要维护的功能(从基础到高级)

Splay部分

需要支持把每个点旋转到它 所在Splay 的根。根定义为:如果这个点和父亲连的是虚边,那它就是它所在 Splay 的根。

一开始 Splay 的根是它维护的实链最上面那个点,然而后期我们会对树做旋转,就导致 Splay 的根可能发生变化。

access(u)

这是最基本的:提取 \(u\) 到根的路径(把路径上都变成实边,并把相应的边变成虚边)。另外,它会把 \(u\) 下面的那个实边给变虚,这就使得 \(u\) 所在的 Splay 恰好变成 \(u\) 到根的路径,不多任何别的。

想起来很简单,每次把 \(u\) 给 \(Splay\) 到最顶上,然后在右儿子连接上一个跨过去的 Splay。特殊地,跨过的第一个 Splay 连空点 —— 这就把 \(u\) 下面那个实边变虚了。

为什么连右儿子?考虑现在的 Splay 维护的链是 \(a\),上一个跨过去的链是 \(a'\)

那么我们既然要把边变成实的,就要把 \(a\) 和 \(a'\) 连起来,其中 \(a'\) 在 \(a\) 的后面,因为这次的链在上次的链上面。

所以要接在 后面,体现在 Splay 的中序遍历,就是接在 右儿子

此时顺便 update 一下信息。

make(u)

把 \(u\) 变成当前树的根。

由上面所说,先 access 一下,Splay 上去。此时由 Splay 的中序遍历,我们是从最下面翻上来的,所以 \(u\) 应该在 Splay 的根,并且其他点都在它的左边(即 \(u\) 的右儿子为空)

然后打一个 lazytag​ 维护边的反转即可。

find(u)

老样子,先 access 一下,然后 Splay 上去

上面说了,现在所有点都在 \(u\) 左边,并且原来的那个根应该在最左边 —— 它是原树中链的顶头,在 Splay 上便是最左边的了。

然后一直走左儿子,就可以找到根了。最后找到根完了再 Splay 上去,保证复杂度

split(u,v)

打通 \(u,v\) 之间的路径,并且不剩别的

先把 \(u\) 变成根(make(u)),然后把 v 给 access 上去即可。Splay 一下保证复杂度。

link(u,v)

终于到了正题

上面说不好直接连,现在我们会换根了,make一下,然后连就可以了

注意,题目如果不保证连边合法,先 find​ 一下看看在不在一块

cut(u,v)

正题*2

先make(u)一下,然后来一个find(v)

find的时候要做一次access,最后把找到的根又 Splay 上去了。那么现在一定满足:

  • \(u\) 是根节点
  • \(u\) 只有一个儿子 \(v\),并且是右儿子(由于 \(u\) 在 \(v\) 上面)。
  • \(u,v\) 的路径中间没有其他的点,体现在 Splay 上,就是 \(v\) 没有左儿子

然后把 \(u\) 的右儿子和 \(v\) 的父亲都设置为空,即可

复杂度

log的,不知道咋分析

网上的势能分析看不懂qaq

为何我的眼里常含泪水,因为我菜的抠脚 —— LightningUZ

talk is cheap

洛谷板子

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. namespace Flandre_Scarlet
  4. {
  5. #define N 1000006
  6. #define F(i,l,r) for(int i=l;i<=r;++i)
  7. #define D(i,r,l) for(int i=r;i>=l;--i)
  8. #define Fs(i,l,r,c) for(int i=l;i<=r;c)
  9. #define Ds(i,r,l,c) for(int i=r;i>=l;c)
  10. #define MEM(x,a) memset(x,a,sizeof(x))
  11. #define FK(x) MEM(x,0)
  12. #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
  13. #define p_b push_back
  14. #define sz(a) ((int)a.size())
  15. #define all(a) a.begin(),a.end()
  16. #define iter(a,p) (a.begin()+p)
  17. int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
  18. template <typename T> void Rd(T& arg){arg=I();}
  19. template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
  20. void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
  21. class Link_Cut_Tree
  22. {
  23. public:
  24. int ch[N][2],fa[N]; // 基本, 维护splay的
  25. bool rv[N]; // makeroot要用的翻转标记
  26. int val[N],s[N]; // 其它要维护的 (这里是路径异或)
  27. #define ls(u) ch[u][0]
  28. #define rs(u) ch[u][1]
  29. bool id(int u) {return u==rs(fa[u]);}
  30. bool rt(int u) {return u!=ls(fa[u]) and u!=rs(fa[u]);}
  31. // 判断是否是 splay 的根
  32. void up(int u) // 维护信息
  33. {
  34. s[u]=s[ls(u)]^s[rs(u)]^val[u];
  35. }
  36. void revone(int u)
  37. {
  38. swap(ls(u),rs(u));
  39. rv[u]^=1;
  40. }
  41. void pushdown(int u) // pushdown
  42. {
  43. if (rv[u])
  44. {
  45. if (ls(u)) revone(ls(u));
  46. if (rs(u)) revone(rs(u));
  47. rv[u]=0;
  48. }
  49. }
  50. void pushall(int u) // 一连串的pushdown
  51. {
  52. if (!rt(u)) pushall(fa[u]);
  53. pushdown(u);
  54. }
  55. void rot(int u) // 最基本的旋转
  56. {
  57. int f=fa[u],f2=fa[f],i=id(u),i2=id(f),w=ch[u][i^1];
  58. if (!rt(f)) ch[f2][i2]=u; ch[u][i^1]=f; ch[f][i]=w;
  59. if (w) fa[w]=f; fa[f]=u; fa[u]=f2;
  60. // 这两行顺序不能反
  61. // 见洛谷讨论区
  62. up(f),up(u);
  63. }
  64. void splay(int u)
  65. {
  66. pushall(u);
  67. while(!rt(u)) // 这里是 while(!rt(u)),而不是 while(u)
  68. {
  69. int f=fa[u];
  70. if (!rt(f))
  71. {
  72. rot(id(f)==id(u)?f:u);
  73. }
  74. rot(u);
  75. }
  76. up(u);
  77. }
  78. void access(int u) // access
  79. {
  80. for(int p=0;u;u=fa[p=u]) // p: 上一块跨过的splay的根
  81. {
  82. splay(u); rs(u)=p; up(u);
  83. }
  84. }
  85. void make(int u)
  86. {
  87. access(u); splay(u); revone(u);
  88. }
  89. int find(int u)
  90. {
  91. access(u); splay(u);
  92. while(ls(u)) pushdown(u),u=ls(u); // 这里不要忘了先pushdown
  93. splay(u); return u; // 也不要忘了splay
  94. }
  95. void split(int u,int v) // 提取路径
  96. {
  97. make(u); access(v); splay(v);
  98. }
  99. void link(int u,int v)
  100. {
  101. make(u);
  102. if (find(v)!=u) // 判一下find
  103. {
  104. fa[u]=v;
  105. }
  106. }
  107. void cut(int u,int v)
  108. {
  109. make(u);
  110. if (find(v)==u and rs(u)==v and !ls(v)) // 判一下find*2
  111. {
  112. rs(u)=fa[v]=0;
  113. }
  114. }
  115. }T;
  116. int n,m;
  117. void Input()
  118. {
  119. Rd(n,m);
  120. F(i,1,n) T.val[i]=I();
  121. }
  122. void Sakuya()
  123. {
  124. F(i,1,m)
  125. {
  126. int o,x,y; Rd(o,x,y);
  127. if (o==0)
  128. {
  129. T.split(x,y);
  130. printf("%d\n",T.s[y]);
  131. }
  132. if (o==1)
  133. {
  134. T.link(x,y);
  135. }
  136. if (o==2)
  137. {
  138. T.cut(x,y);
  139. }
  140. if (o==3)
  141. {
  142. T.splay(x);
  143. T.val[x]=y;
  144. }
  145. }
  146. }
  147. void IsMyWife()
  148. {
  149. Input();
  150. Sakuya();
  151. }
  152. }
  153. #undef int //long long
  154. int main()
  155. {
  156. Flandre_Scarlet::IsMyWife();
  157. getchar();
  158. return 0;
  159. }

省选复习 - LCT 笔记的更多相关文章

  1. PJ可能会用到的动态规划选讲-学习笔记

    PJ可能会用到的动态规划选讲-学习笔记 by Pleiades_Antares 难度和速度全部都是按照普及组来定的咯 数位状压啥就先不讲了 这里主要提到的都是比较简单的DP 一道思维数学巧题(补昨天) ...

  2. JavaScript、全选反选-课堂笔记

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. 算法复习——LCT(bzoj2049洞穴勘测)

    题目: Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连 ...

  4. LCT笔记

    先存个代码 #include<iostream> #include<cstring> #include<cstdio> #include<cmath> ...

  5. FHQ treap学习(复习)笔记

    .....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...

  6. 应聘复习基础笔记1:网络编程之TCP与UDP的优缺点,TCP三次握手、四次挥手、传输窗口控制、存在问题

    重要性:必考 一.TCP与UDP的优缺点 ①TCP---传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据.TCP提供 ...

  7. tarjan学习(复习)笔记(持续更新)(各类找环模板)

    题目背景 缩点+DP 题目描述 给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只 ...

  8. LCT做题笔记

    最近几天打算认真复习LCT,毕竟以前只会板子.正好也可以学点新的用法,这里就用来写做题笔记吧.这个分类比较混乱,主要看感觉,不一定对: 维护森林的LCT 就是最普通,最一般那种的LCT啦.这类题目往往 ...

  9. C#复习笔记(5)--C#5:简化的异步编程(异步编程的深入分析)

    首先,阐明一下标题的这个“深入分析”起得很惭愧,但是又不知道该起什么名字,这个系列也主要是做一些复习的笔记,供自己以后查阅,如果能够帮助到别人,那自然是再好不过了. 然后,我想说的是异步方法的状态机真 ...

随机推荐

  1. Linux课程知识点总结(二)

    Linux课程知识点总结(二) 七.Shell实用功能 7.1 命令行自动补全 在Linux系统中,有太多的命令和文件名称需要记忆,使用命令行补全功能[Tab]可以快速的写出文件名和命令名 7.2 命 ...

  2. device_create为何可以在/sys/class/具体类/属性文件?怎么实现的

    答案: 版本3.6.9: device_create -> device_register -> device_add -> device_add_attrs -> devic ...

  3. 母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列

    简介 多线程通信一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天我们以实际生活中母鸡下蛋案例用代码剖析下实现过程.母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个 ...

  4. vue-element Form表单验证(表单验证没错却一直提示错误)

    在使用element-UI 的表单时,发生一个验证错误,例如已输入值但求验证纠错:       代码如下所示: <el-form :model="correction" :i ...

  5. 深入理解linux-free命令原理(2)

    linux free 命令用法说明 概述: 这篇文章比较深入的从free为起点  折射出的一些概念:比如  buff/cache是怎么一回事[涉及内存页等话题]:  available这个参数与fre ...

  6. TurtleBot3 Waffle (tx2版华夫)(7)底盘测试

    说明:opencr本身带有自测底盘功能,通过按opencr的sw1和sw2来自检底盘是否正确安装和运行: 7.1.前进测试 1)测试前,先把小车架空,轮子不要着地: 2)接好电源后,打开opencr的 ...

  7. jpa 主键重复导致查询list的数据总是重复第一条数据

    背境: JPA 读取 Oracle 中的视图,同一条sql, 在数据库 IDE (PLSql)读出 878 条记录并正常显示,代码依然保存了 878 条记录,但所有记录均一样,即数据库中第一条记录. ...

  8. java操作hive和beeline的使用

    一.java操作hive 1.启动服务:hiveserver2,让hive开启与外部连接的服务 nohup hiveserver2 1>/dev/null 2>/dev/null & ...

  9. Linux运维入门到高级全套系列PDF

    Linux运维入门到高级全套系列PDF(转) [日期:2016-08-01] 来源:Linux社区  作者:Linux [字体:大 中 小]     Linux 学习技巧 初学者可以自己安装虚拟机,然 ...

  10. netty心跳检测机制

    既然是网络通信那么心跳检测肯定是离不开的,netty心跳检测分为读.写.全局 bootstrap.childHandler(new ChannelInitializer<SocketChanne ...