OSDN Git Service

Update lct.md
authorhewenyang <vincent.163@outlook.com>
Mon, 29 Jul 2019 13:52:30 +0000 (21:52 +0800)
committerhewenyang <vincent.163@outlook.com>
Mon, 29 Jul 2019 13:52:30 +0000 (21:52 +0800)
docs/ds/lct.md
docs/graph/lca.md

index 6b305fc..86eaf9b 100644 (file)
@@ -2,7 +2,7 @@
 
 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$ 间的路径,方便做区间操作。
 
@@ -213,10 +213,12 @@ inline void Splay(int x) {
 // 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;
 }
 ```
 
@@ -262,10 +264,12 @@ inline void Access(int x) {
 
 ```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;
 }
 ```
 
@@ -276,6 +280,12 @@ inline void Access(int x) {
 3.  更新当前点的信息。
 4.  把当前点换成当前点的父亲,继续操作。
 
+这里提供的 Access 还有一个返回值。这个返回值相当于最后一次虚实链变换时虚边父亲节点的值。该值有两个含义:
+
+* 连续两次 Access 操作时,第二次 Access 操作的返回值等于这两个节点的 LCA.
+
+* 表示 $x$ 到根的链所在的 Splay 树的根。这个节点一定已经被旋转到了根节点,且父亲一定为空。
+
 ###  `Update()` 
 
 ```cpp
@@ -291,14 +301,15 @@ void Update(int p) {
 -    `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;
 }
 ```
index 27f1444..d6a7d03 100644 (file)
@@ -5,6 +5,8 @@
 
 ## 性质
 
+> 本节 **性质** 部分内容翻译自[wcipeg](http://wcipeg.com/wiki/Lowest_common_ancestor),并做过修改。
+
 1.   $\text{LCA}({u})=u$ ;
 2.   $u$ 是 $v$ 的祖先,当且仅当 $\text{LCA}(u,v)=u$ ;
 3.  如果 $u$ 不为 $v$ 的祖先并且 $v$ 不为 $u$ 的祖先,那么 $u,v$ 分别处于 $\text{LCA}(u,v)$ 的两棵不同子树中;
@@ -241,9 +243,9 @@ void dfs(int x, int dep) {
 
 LCA 为两个游标跳转到同一条重链上时深度较小的那个游标所指向的点。
 
-### 动态树
+### [动态树](/ds/lct)
 
-> 本节 **性质** 部分内容翻译自[wcipeg](http://wcipeg.com/wiki/Lowest_common_ancestor),并做过修改。
+设连续两次 [access](/ds/lct/#access) 操作的点分别为 `u` 和 `v`,则第二次 [access](/ds/lct/#access) 操作返回的点即为 `u` 和 `v` 的 LCA.
 
 ### 标准 RMQ