1. Link/Cut Tree 是一种数据结构,我们用它来解决<font color="red">动态树问题</font>。
2. Link/Cut Tree 又称 Link-Cut Tree,简称 LCT, 但它不叫动态树,动态树是指一类问题。
-3. Splay Tree 是 LCT 的基础,但是 LCT ⽤的 Splay Tree 和普通的 Splay 在细节处不太一样。
+3. Splay Tree 是 LCT 的基础,但是 LCT ⽤的 Splay Tree 和普通的 Splay 在细节处不太一样(进行了一些扩展)。
4. 这是⼀个和 Splay ⼀样只需要写⼏ (yi) 个 (dui) 核心函数就能实现一切的数据结构。
## 问题引入
- 查询某点子树权值和。
唔,看上去是一道树剖模版题。
-é\82£ä¹\88æ\88\91们å\8a 两个操作
+é\82£ä¹\88æ\88\91们å\8a ä¸\80个操作
- 断开并连接一些边,保证仍是一棵树。
-- 在线求出上面的答案。
+
+要求在线求出上面的答案。
——动态树问题的解决方法:Link/Cut Tree!
## - 辅助树
-- æ\88\91们å\88\9aæ\89\8då\9c¨è¯´ç\9a\84建æ \91æ\96¹æ³\95ï¼\8cå\85¶å®\9eå°±æ\98¯è¾\85å\8a©æ \91ç\9a\84建æ \91æ\96¹æ³\95ï¼\8cæ\88\91们å\85\88æ\9d¥ 看⼀看辅助树的一些性质,再通过一张图实际了解一下辅助树的具体结构。
+- æ\88\91们å\85\88æ\9d¥看⼀看辅助树的一些性质,再通过一张图实际了解一下辅助树的具体结构。
-1. 每⼀个 Splay 维护的是一条路径,并且在原树中所有节点深度严格递增,并且,中序遍历这棵 Splay 得到的点序列列的点深度严格递增。
-2. 每个节点包含且仅包含于一棵 Splay 中。
-3. ⼀棵 Splay 的根节点的 Father 指向它在辅助树中的父亲结点。但是它父亲结点的 ch 并没有指向这个点的。即父亲不不⼀定认⼉子,⽽⼉子能找到⽗亲。
-4. 由于 LCT 的 `Access` 操作(后面会解释),使得 3. 中的⽗亲不认⼉子对答案⽆任何影响,同时,也使一些叶⼦结点单独构成一棵 Splay 辅助树成为可能
-5. 由于辅助树的以上性质,我们维护任何操作都不不需要维护原树,辅助树可以在任何情况下拿出一个唯一的原树,我们只需要维护辅助树即可。(本句来源自 大爷 @PoPoQQQ 的 PPT)
+- 在本文里,你可以认为一些 Splay 构成了一个辅助树,每棵辅助树维护的是一棵树,一些辅助树构成了 LCT,其维护的是整个森林。
-在本文里,你可以认为一些 Splay 构成了一个辅助树,每棵辅助树维护的是一棵树,一些辅助树构成了 LCT,其维护的是整个森林。
+1. 辅助树由多棵 Splay 组成,每棵 Splay 维护原树中的一条路径,且中序遍历这棵 Splay 得到的点序列,从前到后对应原树“从上到下”的一条路径。
+2. 原树每个节点与辅助树的 Splay 节点一一对应。
+3. 辅助树的各棵 Splay 之间并不是独立的。每棵 Splay 的根节点的父亲节点本应是空,但在 LCT 中每棵 Splay 的根节点的父亲节点指向原树中*这条链*的父亲节点(即链最顶端的点的父亲节点)。这类父亲链接与通常 Splay 的父亲链接区别在于儿子认父亲,而父亲不认儿子,对应原树的一条*虚边*。因此,每个联通块恰好有一个点的父亲节点为空。
+4. 由于辅助树的以上性质,我们维护任何操作都不需要维护原树,辅助树可以在任何情况下拿出一个唯一的原树,我们只需要维护辅助树即可。(本句来源自 大爷 @PoPoQQQ 的 PPT)
- 现在我们有⼀棵原树,如图。
- 加粗边是实边,虚线边是虚边。
#### 新操作
-1. `IsRoot(x)` 判断当前节点是否是所在 Splay 的根。
-2. `Access(x)` 把从根到当前节点的所有点放在⼀条实链里,使根到它成为一条实路径,并且在同一棵 Splay 里。
+1. `Access(x)` 把从根到 $x$ 的所有点放在⼀条实链里,使根到 $x$ 成为一条实路径,并且在同一棵 Splay 里。**只有此操作是必须实现的,其他操作视题目而实现。**
+2. `IsRoot(x)` 判断 $x$ 是否是所在树的根。
3. `Update(x)` 在 `Access` 操作之后,递归地从上到下 `Pushdown` 更新信息。
-4. `MakeRoot(x)` 使 $x$ 点成为整棵辅助树的根。
-5. `Link(x, y)` 在 $x, y$ 两点间连⼀一条边。
+4. `MakeRoot(x)` 使 $x$ 点成为其所在树的根。
+5. `Link(x, y)` 在 $x, y$ 两点间连一条边。
6. `Cut(x, y)` 把 $x, y$ 两点间边删掉。
-7. `Find(x)` 找到 $x$ 所在的 Splay 的根节点编号。
+7. `Find(x)` 找到 $x$ 所在树的根节点编号。
8. `Fix(x, v)` 修改 $x$ 的点权为 $v$ 。
9. `Split(x, y)` 提取出 $x, y$ 间的路径,方便做区间操作。
// Access 是 LCT
// 的核心操作,试想我们像求解一条路径,而这条路径恰好就是我们当前的一棵 Splay,
// 直接调用其信息即可。先来看一下代码,再结合图来看看过程
-inline void Access(int x) {
- for (int p = 0; x; p = x, x = f[x]) {
+inline int Access(int x) {
+ int p;
+ for (p = 0; x; p = x, x = f[x]) {
Splay(x), ch[x][1] = p, PushUp(x);
}
+ return p;
}
```
```cpp
// 回顾一下代码
-inline void Access(int x) {
- for (int p = 0; x; p = x, x = f[x]) {
+inline int Access(int x) {
+ int p;
+ for (p = 0; x; p = x, x = f[x]) {
Splay(x), ch[x][1] = p, PushUp(x);
}
+ return p;
}
```
3. 更新当前点的信息。
4. 把当前点换成当前点的父亲,继续操作。
+这里提供的 Access 还有一个返回值。这个返回值相当于最后一次虚实链变换时虚边父亲节点的值。该值有两个含义:
+
+* 连续两次 Access 操作时,第二次 Access 操作的返回值等于这两个节点的 LCA.
+
+* 表示 $x$ 到根的链所在的 Splay 树的根。这个节点一定已经被旋转到了根节点,且父亲一定为空。
+
### `Update()`
```cpp
- `Make_Root()` 的重要性丝毫不亚于 `Access()` 。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 AuxTree 的性质,这种路径是不能出现在一棵 Splay 中的。
- 这时候我们需要用到 `Make_Root()` 。
- `Make_Root()` 的作用是使指定的点成为原树的根,考虑如何实现这种操作。
-- 我们发现 `Access(x)` 后, $x$ 在 Splay 中一定是深度最大的点(从根到 $x$ ,深度严格递增)。
-- 而变成根即是变成深度最小的点。我们 `Splay(x)` , 发现这时候 $x$ 并没有右子树(即所有点深度都比它浅)。那我们把 $x$ 的左右儿子交换一下,变成了 $x$ 没有左子树,在 AuxTree 意义上就是深度最小的点了,即达到目的。
-- 所以我们交换左右儿子,并给右儿子打一个翻转标记即可。(此时左儿子没有值)。
+- 设 `Access(x)` 的返回值为 $y$,则此时 $x$ 到当前根的路径恰好构成一个 Splay,且该 Splay 的根为 $y$.
+- 考虑将树用有向图表示出来,给每条边定一个方向,表示从儿子到父亲的方向。容易发现换根相当于将 $x$ 到根的路径的所有边反向(请仔细思考)。
+- 因此将 $x$ 到当前根的路径翻转即可。
+- 由于 $y$ 是 $x$ 到当前根的路径所代表的 Splay 的根,因此将以 $y$ 为根的 Splay 树进行区间翻转即可。
```cpp
inline void makeRoot(int p) {
- Access(p), Splay(p);
- swap(ls, rs);
+ p = Access(p);
+ swap(ch[p][0], ch[p][1]);
tag[p] ^= 1;
}
```