OSDN Git Service

Update lct.md
author雷蒻 <34390285+hsfzLZH1@users.noreply.github.com>
Sun, 21 Jul 2019 05:18:01 +0000 (13:18 +0800)
committerGitHub <noreply@github.com>
Sun, 21 Jul 2019 05:18:01 +0000 (13:18 +0800)
句号, $x$ ,区分 Splay 操作和数据结构 Splay ,统一操作名。

docs/ds/lct.md

index a5e958e..4482f76 100644 (file)
@@ -1,6 +1,6 @@
 ## 简介
 
-1.  Link/Cut Tree 是一种数据结构,我们用它来解决<font color="red">动态树问题</font>
+1.  Link/Cut Tree 是一种数据结构,我们用它来解决<font color="red">动态树问题</font> 。
 2.  Link/Cut Tree 又称 Link-Cut Tree,简称 LCT, 但它不叫动态树,动态树是指一类问题。
 3.  Splay Tree 是 LCT 的基础,但是 LCT ⽤的 Splay Tree 和普通的 Splay 在细节处不太一样。
 4.  这是⼀个和 Splay ⼀样只需要写⼏ (yi) 个 (dui) 核心函数就能实现一切的数据结构。
 1.  每⼀个 Splay 维护的是一条路径,并且在原树中所有节点深度严格递增,并且,中序遍历这棵 Splay 得到的点序列列的点深度严格递增。
 2.  每个节点包含且仅包含于一棵 Splay 中。
 3.  ⼀棵 Splay 的根节点的 Father 指向它在辅助树中的父亲结点。但是它父亲结点的 ch 并没有指向这个点的。即父亲不不⼀定认⼉子,⽽⼉子能找到⽗亲。
-4.  由于 LCT 的 Access 操作(后面会解释),使得 3. 中的⽗亲不认⼉子对答案⽆任何影响,同时,也使一些叶⼦结点单独构成一棵 Splay 辅助树成为可能
+4.  由于 LCT 的 `Access` 操作(后面会解释),使得 3. 中的⽗亲不认⼉子对答案⽆任何影响,同时,也使一些叶⼦结点单独构成一棵 Splay 辅助树成为可能
 5.  由于辅助树的以上性质,我们维护任何操作都不不需要维护原树,辅助树可以在任何情况下拿出一个唯一的原树,我们只需要维护辅助树即可。(本句来源自 大爷 @PoPoQQQ 的 PPT)
 
 在本文里,你可以认为一些 Splay 构成了一个辅助树,每棵辅助树维护的是一棵树,一些辅助树构成了 LCT,其维护的是整个森林。
 
 -   现在我们有⼀棵原树,如图。
--   加粗边是实边,虚线边是虚边
+-   加粗边是实边,虚线边是虚边
 
 ![lct9](./images/lct9.png)
 
@@ -88,7 +88,7 @@
 
 ### 考虑原树和辅助树的结构关系
 
--   原树中的实链 : 在辅助树中节点都在一棵 Splay 中
+-   原树中的实链 : 在辅助树中节点都在一棵 Splay 中
 
 -   原树中的虚链 : 在辅助树中,子节点所在 Splay 的 Father 指向父节点,但是父节点的两个儿子都不指向子节点。
 
 
 #### ⼀般数据结构函数(字面意思)
 
-1.  PushUp(x)
-2.  PushDown(x)
+1.  `PushUp(x)`
+2.  `PushDown(x)`
 
 #### Splay 系函数(不会多做解释)
 
-1.  Get(x) 获取 x 是父亲的哪个⼉子。
-2.  Splay(x) 通过和 Rotate 操作联动实现把 x 旋转到<font color = "red">当前 Splay 的根。</font>
-3.  Rotate(x) 将 x 向上旋转一层的操作。
+1.  `Get(x)` 获取 x 是父亲的哪个⼉子。
+2.  `Splay(x)` 通过和 Rotate 操作联动实现把 x 旋转到<font color = "red">当前 Splay 的根</font>。
+3.  `Rotate(x)` 将 x 向上旋转一层的操作。
 
 #### 新操作
 
-1.  IsRoot(x) 判断当前节点是否是所在 Splay 的根。
-2.  Access(x) 把从根到当前节点的所有点放在⼀条实链里,使根到它成为一条实路径,并且在同一棵 Splay 里。
-3.  Update(x) 在 Access 操作之后,递归地从上到下 Pushdown 更新信息。
-4.  MakeRoot(x) 使 x 点成为整棵辅助树的根。
-5.  Link(x, y) 在 x, y 两点间连⼀一条边。
-6.  Cut(x, y) 把 x, y 两点间边删掉。
-7.  Find(x) 找到 x 所在的 Splay 的根节点编号。
-8.  Fix(x, v) 修改 x 的点权为 v。
-9.  Split(x, y) 提取出 x, y 间的路径,方便做区间操作。
+1.  `IsRoot(x)` 判断当前节点是否是所在 Splay 的根。
+2.  `Access(x)` 把从根到当前节点的所有点放在⼀条实链里,使根到它成为一条实路径,并且在同一棵 Splay 里。
+3.  `Update(x)` 在 Access 操作之后,递归地从上到下 Pushdown 更新信息。
+4.  `MakeRoot(x)` 使 x 点成为整棵辅助树的根。
+5.  `Link(x, y)` 在 x, y 两点间连⼀一条边。
+6.  `Cut(x, y)` 把 x, y 两点间边删掉。
+7.  `Find(x)` 找到 x 所在的 Splay 的根节点编号。
+8.  `Fix(x, v)` 修改 x 的点权为 v。
+9.  `Split(x, y)` 提取出 x, y 间的路径,方便做区间操作。
 
 ### 宏定义
 
@@ -174,7 +174,7 @@ inline void PushDown(int p) {
 
 ###  `Splay() && Rotate()` 
 
-有些不一样了哦
+有些不一样了哦
 
 ```cpp
 #define Get(x) (ch[f[x]][1] == x)
@@ -195,9 +195,9 @@ inline void Splay(int x) {
 }
 ```
 
-如果上面的几个函数你看不懂,请移步[Splay](/ds/splay/)
+如果上面的几个函数你看不懂,请移步[Splay](/ds/splay/) 。
 
-下面要开始 LCT 独有的函数了哦
+下面要开始 LCT 独有的函数了哦
 
 ###  `isRoot()` 
 
@@ -220,43 +220,43 @@ inline void Access(int x) {
 }
 ```
 
-我们有这样一棵树,实线为实边,虚线为虚边
+我们有这样一棵树,实线为实边,虚线为虚边
 
 ![pic1](./images/lct1.png)
 
--   它的辅助树可能长成这样(构图方式不同可能 LCT 的结构也不同)
+-   它的辅助树可能长成这样(构图方式不同可能 LCT 的结构也不同)
 -   每个绿框里是一棵 Splay。
 
 ![pic2](./images/lct2.png)
 
--   现在我们要 Access(N), 把 A 到 N 路径上的边都变为实边,拉成一棵 Splay
+-   现在我们要 `Access(N)`, 把 $A$ 到 $N$ 路径上的边都变为实边,拉成一棵 Splay。
 
 ![pic3](./images/lct3.png)
 
--   实现的方法是从下到上逐步更新 Splay
--   首先我们要把 N 旋至当前 Splay 的根。
--   为了保证 AuxTree(辅助树)的性质,原来 N 到 O 的实边要更改为虚边。
--   由于认父不认子的性质,我们可以单方面的把 N 的儿子改为 Null。
+-   实现的方法是从下到上逐步更新 Splay
+-   首先我们要把 $N$ 旋至当前 Splay 的根。
+-   为了保证 AuxTree(辅助树)的性质,原来 $N$ 到 $O$ 的实边要更改为虚边。
+-   由于认父不认子的性质,我们可以单方面的把 $N$ 的儿子改为 Null。
 -   于是原来的 AuxTree 就从下图变成了下下图。
 
 ![pic4](./images/lct4.png)
 
 ![pic](./images/lct5.png)
 
--   下一步,我们把 N 指向的 Father-> I 也旋转到它 (I) 的 Splay 树根。
+-   下一步,我们把 $N$ 指向的 Father $I$ 也旋转到 $I$ 的 Splay 树根。
 
--   原来的实边 I—K 要去掉,这时候我们把 I 的右儿子指向 N, 就得到了 I—L 这样一棵 Splay。
+-   原来的实边 $I$ — $K$ 要去掉,这时候我们把 $I$ 的右儿子指向 $N$, 就得到了 $I$ — $L$ 这样一棵 Splay。
 
 ![pic](./images/lct8.png)
 
--   接下来,按照刚刚的操作步骤,由于 I 的 Father 指向 H, 我们把 H 旋转到他所在 Splay Tree 的根,然后把 H 的 rs 设为 I
+-   接下来,按照刚刚的操作步骤,由于 $I$ 的 Father 指向 $H$, 我们把 $H$ 旋转到他所在 Splay Tree 的根,然后把 $H$ 的 rs 设为 $I$
 
 -   之后的树是这样的。
 
 ![pic](./images/lct6.png)
 
--   同理我们 Splay(A) , 并把 A 的右儿子指向 H
--   于是我们得到了这样一棵 AuxTree。并且发现 A——N 的整个路径已经在同一棵 Splay 中了。大功告成!
+-   同理我们 `Splay(A)` , 并把 $A$ 的右儿子指向 $H$
+-   于是我们得到了这样一棵 AuxTree。并且发现 $A$ — $N$ 的整个路径已经在同一棵 Splay 中了。大功告成!
 
 ![pic](./images/lct7.png)
 
@@ -269,7 +269,7 @@ inline void Access(int x) {
 }
 ```
 
-我们发现 Access() 其实很容易。只有如下四步操作:
+我们发现 `Access()` 其实很容易,只有如下四步操作:
 
 1.  把当前节点转到根。
 2.  把儿子换成之前的节点。
@@ -288,11 +288,11 @@ void Update(int p) {
 
 ###  `makeRoot()` 
 
--   Make_Root() 的重要性丝毫不亚于 Access()。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 AuxTree 的性质,这种路径是不能出现在一棵 Splay 中的。
--   这时候我们需要用到 Make_Root()
--   Make_Root() 的作用是使指定的点成为原树的根,考虑如何实现这种操作。
--   我们发现 Access(x) 后,x 在 Splay 中一定是深度最大的点(从根到 x, 深度严格递增)。
--   而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树(即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下,变成了 x 没有左子树,在 AuxTree 意义上就是深度最小的点了,即达到目的。
+-   `Make_Root()` 的重要性丝毫不亚于 `Access()`。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 AuxTree 的性质,这种路径是不能出现在一棵 Splay 中的。
+-   这时候我们需要用到 `Make_Root()`
+-   `Make_Root()` 的作用是使指定的点成为原树的根,考虑如何实现这种操作。
+-   我们发现 `Access(x)` 后,$x$ 在 Splay 中一定是深度最大的点(从根到 $x$ , 深度严格递增)。
+-   而变成根即是变成深度最小的点。我们 `Splay(x)` , 发现这时候 $x$ 并没有右子树(即所有点深度都比它浅)。那我们把 $x$ 的左右儿子交换一下,变成了 $x$ 没有左子树,在 AuxTree 意义上就是深度最小的点了,即达到目的。
 -   所以我们交换左右儿子,并给右儿子打一个翻转标记即可。(此时左儿子没有值)。
 
 ```cpp
@@ -305,7 +305,7 @@ inline void makeRoot(int p) {
 
 ###  `Link()` 
 
--   Link 两个点其实很简单,先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然,这个操作肯定不能发生在同一棵树内,所以记得先判一下。
+-   Link 两个点其实很简单,先 `Make_Root(x)` , 然后把 $x$ 的父亲指向 $y$ 即可。显然,这个操作肯定不能发生在同一棵树内,所以记得先判一下。
 
 ```cpp
 inline void Link(int x, int p) {
@@ -316,15 +316,15 @@ inline void Link(int x, int p) {
 
 ###  `Split()` 
 
--   Split 操作意义很简单,就是拿出一棵 Splay , 维护的是 x 到 y 的路径。
--   先 MakeRoot(x),然后 Access(y)。如果要 y 做根,再 Splay(y)
+-   `Split` 操作意义很简单,就是拿出一棵 Splay , 维护的是 $x$ 到 $y$ 的路径。
+-   先 `MakeRoot(x)`,然后 `Access(y)`。如果要 $y$ 做根,再 `Splay(y)`
 -   就这三句话,没写代码,需要的时候可以直接打这三个就好辣!
--   另外 Split 这三个操作直接可以把需要的路径拿出到 y 的子树上,那不是随便干嘛咯。
+-   另外 Split 这三个操作直接可以把需要的路径拿出到 $y$ 的子树上,那不是随便干嘛咯。
 
 ###  `Cut()` 
 
--   Cut 有两种情况,保证合法和不一定保证合法。(废话)
--   如果保证合法,直接 split(x, y),这时候 y 是根,x 一定是它的儿子,双向断开即可。就像这样:
+-   `Cut` 有两种情况,保证合法和不一定保证合法。(废话)
+-   如果保证合法,直接 `Split(x, y)`,这时候 $y$ 是根,$x$ 一定是它的儿子,双向断开即可。就像这样:
 
 ```cpp
 inline void Cut(int x, int p) {
@@ -332,20 +332,21 @@ inline void Cut(int x, int p) {
 }
 ```
 
-如果是不保证合法,我们需要判断一下是否有,我选择使用 map 存一下,但是这里有一个利用性质的方法:
+如果是不保证合法,我们需要判断一下是否有,我选择使用 `map` 存一下,但是这里有一个利用性质的方法:
 
 想要删边,必须要满足如下三个条件:
 
-1.  x, y 连通。
-2.  x, y 的路径上没有其他的链。
-3.  x 没有右儿子。
-4.  总结一下,上面三句话的意思就一个:x, y 之间有边。
+1.  $x,y$ 连通。
+2.  $x,y$ 的路径上没有其他的链。
+3.  $x$ 没有右儿子。
 
-具体实现就留作一个思考题给大家。判断连通需要用到后面的 Find , 其他两点稍作思考分析一下结构就知道该怎么判断了。
+总结一下,上面三句话的意思就一个:$x,y$ 之间有边。
+
+具体实现就留作一个思考题给大家。判断连通需要用到后面的 `Find` , 其他两点稍作思考分析一下结构就知道该怎么判断了。
 
 ###  `Find()` 
 
--   Find() 其实就是找到当前辅助树的根。在 Access(p) 后,再 splay(p)。这样根就是树里最小的那个,一直往 ls 走,沿途 PushDown 即可。
+-   `Find()` 其实就是找到当前辅助树的根。在 `Access(p)` 后,再 `Splay(p)`。这样根就是树里最小的那个,一直往 ls 走,沿途 `PushDown` 即可。
 -   一直走到没有 ls, 非常简单。
 
 ```cpp
@@ -358,9 +359,9 @@ inline int Find(int p) {
 
 ### 一些提醒
 
--   干点啥前一定要想一想需不需要 PushUp 或者 PushDown, LCT 由于特别灵活的原因,少 Pushdown 或者 Pushup 一次就可能把修改改到不该改的点上!
--   LCT 的 rotate 和 splay 的不太一样, `if (z)` 一定要放在前面。
--   LCT 的 splay 就是旋转到根,没有旋转到谁儿子的操作,因为不需要。
+-   干点啥前一定要想一想需不需要 `PushUp` 或者 `PushDown`, LCT 由于特别灵活的原因,少 `Pushdown` 或者 `Pushup` 一次就可能把修改改到不该改的点上!
+-   LCT 的 `Rotate` 和 Splay 的不太一样, `if (z)` 一定要放在前面。
+-   LCT 的 `Splay` 操作就是旋转到根,没有旋转到谁儿子的操作,因为不需要。
 
 ## 一些题