## 简介
-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)
### 考虑原树和辅助树的结构关系
-- 原树中的实链 : 在辅助树中节点都在一棵 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 间的路径,方便做区间操作。
### 宏定义
### `Splay() && Rotate()`
-有些不一样了哦
+有些不一样了哦。
```cpp
#define Get(x) (ch[f[x]][1] == x)
}
```
-如果上面的几个函数你看不懂,请移步[Splay](/ds/splay/)
+如果上面的几个函数你看不懂,请移步[Splay](/ds/splay/) 。
-下面要开始 LCT 独有的函数了哦
+下面要开始 LCT 独有的函数了哦。
### `isRoot()`
}
```
-我们有这样一棵树,实线为实边,虚线为虚边
+我们有这样一棵树,实线为实边,虚线为虚边。
![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)
}
```
-我们发现 Access() 其实很容易。只有如下四步操作:
+我们发现 `Access()` 其实很容易,只有如下四步操作:
1. 把当前节点转到根。
2. 把儿子换成之前的节点。
### `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
### `Link()`
-- Link 两个点其实很简单,先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然,这个操作肯定不能发生在同一棵树内,所以记得先判一下。
+- Link 两个点其实很简单,先 `Make_Root(x)` , 然后把 $x$ 的父亲指向 $y$ 即可。显然,这个操作肯定不能发生在同一棵树内,所以记得先判一下。
```cpp
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) {
}
```
-如果是不保证合法,我们需要判断一下是否有,我选择使用 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
### 一些提醒
-- 干点啥前一定要想一想需不需要 PushUp 或者 PushDown, LCT 由于特别灵活的原因,少 Pushdown 或者 Pushup 一次就可能把修改改到不该改的点上!
-- LCT 的 rotate 和 splay 的不太一样, `if (z)` 一定要放在前面。
-- LCT 的 splay 就是旋转到根,没有旋转到谁儿子的操作,因为不需要。
+- 干点啥前一定要想一想需不需要 `PushUp` 或者 `PushDown`, LCT 由于特别灵活的原因,少 `Pushdown` 或者 `Pushup` 一次就可能把修改改到不该改的点上!
+- LCT 的 `Rotate` 和 Splay 的不太一样, `if (z)` 一定要放在前面。
+- LCT 的 `Splay` 操作就是旋转到根,没有旋转到谁儿子的操作,因为不需要。
## 一些题