在复制一个节点 $X_{a}$($X$ 节点的第 $a$ 个版本) 的新版本 $X_{a+1}$ ($X$ 节点的第 $a+1$ 个版本) 以后:
-- 如果某个儿子节点 $Y$ 不用修改信息,那么就把 $X_{a+1}$ 的指针直接指向 $Y_{a}$ ($Y$ 节点的第 $a$ 个版本) 即可。
-- 反之,如果要修改 $Y$ ,那么就在**递归到下层**时**新建** $Y_{a+1}$ ($Y$ 节点的第 $a+1$ 个版本) 这个新节点用于**存储新的信息**,同时把 $X_{a+1}$ 的指针指向 $Y_{a+1}$ ($Y$ 节点的第 $a+1$ 个版本)。
+- 如果某个儿子节点 $Y$ 不用修改信息,那么就把 $X_{a+1}$ 的指针直接指向 $Y_{a}$ ($Y$ 节点的第 $a$ 个版本) 即可。
+- 反之,如果要修改 $Y$ ,那么就在**递归到下层**时**新建** $Y_{a+1}$ ($Y$ 节点的第 $a+1$ 个版本) 这个新节点用于**存储新的信息**,同时把 $X_{a+1}$ 的指针指向 $Y_{a+1}$ ($Y$ 节点的第 $a+1$ 个版本)。
### 可持久化
需要的东西:
-- 一个 struct 数组 存**每个节点**的信息 (一般叫做 tree 数组); (当然写**指针版**平衡树的大佬就可以考虑不用这个数组了)
+- 一个 struct 数组 存**每个节点**的信息 (一般叫做 tree 数组); (当然写**指针版**平衡树的大佬就可以考虑不用这个数组了)
-- 一个**根节点数组**,存每个版本的_树根_,每次查询版本信息时就从**根数组存的节点**开始;
+- 一个**根节点数组**,存每个版本的_树根_,每次查询版本信息时就从**根数组存的节点**开始;
-- split() 分裂 **从树中分裂出两棵树**
+- split() 分裂 **从树中分裂出两棵树**
-- merge() 合并 **把两棵树按照随机权值合并**
+- merge() 合并 **把两棵树按照随机权值合并**
-- newNode() 新建一个节点
+- newNode() 新建一个节点
-- build() 建树
+- build() 建树
#### Split
表示把 $_x$ 为根的树的前 $k$ 个元素放在**一棵树**中,剩下的节点构成在另一棵树中,返回这两棵树的根(first 是第一棵树的根,second 是第二棵树的)。
-- 如果 $x$ 的**左子树**的 $key ≥ k$,那么**直接递归进左子树**,把左子树分出来的第二颗树和当前的 x **右子树**合并。
-- 否则递归**右子树**。
+- 如果 $x$ 的**左子树**的 $key ≥ k$,那么**直接递归进左子树**,把左子树分出来的第二颗树和当前的 x **右子树**合并。
+- 否则递归**右子树**。
```c++
static std::pair<int, int> _split(int _x, int k) {
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作(对于各个以往的历史版本):
-1. 插入 x 数
+1. 插入 x 数
-2. 删除 x 数(若有多个相同的数,因只删除一个,如果没有请忽略该操作)
+2. 删除 x 数(若有多个相同的数,因只删除一个,如果没有请忽略该操作)
-3. 查询 x 数的排名(排名定义为比当前数小的数的个数 + 1。若有多个相同的数,因输出最小的排名)
+3. 查询 x 数的排名(排名定义为比当前数小的数的个数 + 1。若有多个相同的数,因输出最小的排名)
-4. 查询排名为 x 的数
+4. 查询排名为 x 的数
-5. 求 x 的前驱(前驱定义为小于 x,且最大的数,如不存在输出 -2147483647)
+5. 求 x 的前驱(前驱定义为小于 x,且最大的数,如不存在输出 -2147483647)
-6. 求 x 的后继(后继定义为大于 x,且最小的数,如不存在输出 2147483647)
+6. 求 x 的后继(后继定义为大于 x,且最小的数,如不存在输出 2147483647)
和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作 3, 4, 5, 6 即保持原版本无变化)。
### 推荐的练手题
-1. luogu P3919 可持久化数组 (模板题)
+1. luogu P3919 可持久化数组 (模板题)
-2. codeforces 702F T-shirt
+2. codeforces 702F T-shirt
### 另外
-1. 可持久化平衡树可以用来维护动态凸包,仙人掌等东西,如果读者有兴趣可以阅读相应的 [**计算几何**](/geometry) 知识,再来食用。
+1. 可持久化平衡树可以用来维护动态凸包,仙人掌等东西,如果读者有兴趣可以阅读相应的 [**计算几何**](/geometry) 知识,再来食用。
-2. Zip Tree 作为一种新的数据结构在 2018.8 月由 Robert E. Tarjan - Caleb C. Levy - Stephen Timmel 提出,可以去了解一下~
+2. Zip Tree 作为一种新的数据结构在 2018.8 月由 Robert E. Tarjan - Caleb C. Levy - Stephen Timmel 提出,可以去了解一下~
对于一棵有根树,设 $LCA(u,v)=x$,则 $x$ 必须满足以下条件
-- $x$ 是 u 的祖先或 u
+- $x$ 是 u 的祖先或 u
-- $x$ 是 v 的祖先或 v
+- $x$ 是 v 的祖先或 v
-- $x$ 是在满足上面两个条件下深度最大的
+- $x$ 是在满足上面两个条件下深度最大的
显然,在一棵有根树内,对于任意两个节点有且仅有一个 $LCA$
解决这个问题,我们通常有以下方法
-- 树上倍增(本文主要讲解此方法)
+- 树上倍增(本文主要讲解此方法)
-- 转化为 RMQ 问题
+- 转化为 RMQ 问题
-- 树链剖分
+- 树链剖分
-- Tarjan
+- Tarjan
### 暴力做法
一遍 DFS 计算即可
```cpp
-void dfs(int u,int father)
-{
- dep[u]=dep[father]+1; //dep[x] 表示 x 的深度,在查询时会用到
- for (int i=0;i<=19;i++)
- f[u][i+1]=f[f[u][i]][i]; // 预处理
- for (int i=first[u];i;i=next[i]) // 链式前向星
- {
- int v=go[i];
- if (v==father) continue;
- f[v][0]=u; //f[v][0] 表示 v 的父亲
- dfs(v,u);
- }
+void dfs(int u, int father) {
+ dep[u] = dep[father] + 1; // dep[x] 表示 x 的深度,在查询时会用到
+ for (int i = 0; i <= 19; i++) f[u][i + 1] = f[f[u][i]][i]; // 预处理
+ for (int i = first[u]; i; i = next[i]) // 链式前向星
+ {
+ int v = go[i];
+ if (v == father) continue;
+ f[v][0] = u; // f[v][0] 表示 v 的父亲
+ dfs(v, u);
+ }
}
```
只不过在跳的过程中从一步一步跳变成了 ** 一次跳多步 **。可以具体分为以下几步
-1. 让 $x$ 的深度比 $y$ 大(深度在预处理时已经求出)
+1. 让 $x$ 的深度比 $y$ 大(深度在预处理时已经求出)
-2. 将两个节点跳到同一深度。在此处我们使用二进制思想,依次尝试向上跳 $2^i,2^{i-1}\cdots 2^1,2^0$。如果发现则 $x$ 跳到了 $y$ 就说明 $LCA(x,y)=y$
+2. 将两个节点跳到同一深度。在此处我们使用二进制思想,依次尝试向上跳 $2^i,2^{i-1}\cdots 2^1,2^0$。如果发现则 $x$ 跳到了 $y$ 就说明 $LCA(x,y)=y$
-3. 一起往上跳。依然使用二进制思想,让他们一起往上跳 $2^i,2^{i-1}\cdots 2^1,2^0$. 如果 $f[x][i]!=f[y][i]$,说明 $x$ 和 $y$ 还未相遇。最后,$x$ 和 $y$ 必定只差一步相遇。这时 $x$ 的父亲即 $f[x][0]$ 就是他们的 LCA
+3. 一起往上跳。依然使用二进制思想,让他们一起往上跳 $2^i,2^{i-1}\cdots 2^1,2^0$. 如果 $f[x][i]!=f[y][i]$,说明 $x$ 和 $y$ 还未相遇。最后,$x$ 和 $y$ 必定只差一步相遇。这时 $x$ 的父亲即 $f[x][0]$ 就是他们的 LCA
```cpp
-int lca(int x,int y)
-{
- if (dep[x]<dep[y]) swap(x,y); // 步骤 1
- for (int i=20;i>=0;i--) // 步骤 2
- {
- if (dep[f[x][i]]>=dep[y]) x=f[x][i];
- if (x==y) return x;
- }
- for (int i=20;i>=0;i--) // 步骤 3
- if (f[x][i]!=f[y][i])
- {
- x=f[x][i];y=f[y][i];
- }
- return f[x][0];
+int lca(int x, int y) {
+ if (dep[x] < dep[y]) swap(x, y); // 步骤 1
+ for (int i = 20; i >= 0; i--) // 步骤 2
+ {
+ if (dep[f[x][i]] >= dep[y]) x = f[x][i];
+ if (x == y) return x;
+ }
+ for (int i = 20; i >= 0; i--) // 步骤 3
+ if (f[x][i] != f[y][i]) {
+ x = f[x][i];
+ y = f[y][i];
+ }
+ return f[x][0];
}
```