可持久化线段树
Epi4any
·
·
个人记录
可持久化线段树
壹、可持久化数据结构
定义:支持查询历史的数据状态的数据结构。
举例:可持久化并查集,可持久化线段树,可持久化 Trie
贰、可持久化线段树
解法:建 n 颗线段树(小心不要MLE)
- 建多颗线段树(每一颗线段树代表一次修改)
- 相邻两颗线段树之间差别很小,所以每颗线段树在物理上,只需要存储与前一颗线段树的不同之处,使用时填补并生成一颗完整的线段树。
- 任意量颗线段树相减可以得到一颗新的线段树,这颗线段树就是两次之间的改变,往往统计答案也是在这一颗线段树里面查询。
权值线段树必须要写离散化!
练习1:Count On A Tree
题意:给定一个带权树,求 u 和 v 之间链上第 k 小
解法:可持久化线段树上树
$Step2$:对于根到每个节点的链,各建一颗权值线段树子节点比父节点仅增加了一个元素
$Step3$:主席树维护线段树
$Step4$:```Seg[u] + Seg[v] - Seg[lca(u, v)] - Seg[Father[lca(u, v)]]```
#### Luogu P3168 [CQOI2015]任务查询系统
思路:离散化+扫描线+主席树
首先把优先级离散化,将任务差分,区间 $[S_i,E_i]$ 转换成在 $s_i$ 点 $+p_i$ , $e_i+1$ 点 $-p_i$ 然后把上述单点修改操作按照时间排序,建主席树,每个时刻下挂一个权值线段树,记录权值 $i$ 的出现次数(离散化之后)和区间权值(离散化前)之和。
查询的时候在时刻xi的权值线段树上查询区间和即可
这个过程类似于用扫描线在时间轴上从前向后扫描,本题实现过程细节较多,需要注意离散化的数据结构设计
代码模板:
```cpp
const int maxn = 2e5 + 5;
int n, m, a[maxn], b[maxn], root[maxn], len;
struct PSEG_tree {
int ls[maxn << 5], rs[maxn << 5], sum[maxn << 5], cnt;
int Build_tree(int l, int r) {
int cur = ++cnt;
sum[cur] = 0;
if (l >= r) return cur;
int mid = (l + r) >> 1;
ls[cur] = Build_tree(l, mid);
rs[cur] = Build_tree(mid + 1, r);
return cur;
}
int Change(int pre, int l, int r, int x) {
int cur = ++cnt;
ls[cur] = ls[pre], rs[cur] = rs[pre], sum[cur] = sum[pre] + 1;
if (l >= r) return cur;
int mid = (l + r) >> 1;
if (x <= mid) ls[cur] = Change(ls[pre], l, mid, x);
else rs[cur] = Change(rs[pre], mid + 1, r, x);
return cur;
}
int Query(int u, int v, int l, int r, int k) {
if (l >= r) return l;
int x = sum[ls[v]] - sum[ls[u]];
int mid = (l + r) >> 1;
if (x >= k) return Query(ls[u], ls[v], l, mid, k);
else return Query(rs[u], rs[v], mid + 1, r, k);
}
} tree;
void get_discretization() {
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
sort(b + 1, b + n + 1);
len = unique(b + 1, b + n + 1) - b - 1;
root[0] = tree.Build_tree(1, m);
for (int i = 1; i <= n; i++) {
int pos = lower_bound(b + 1, b + len + 1, a[i]) - b;
root[i] = tree.Change(root[i - 1], 1, m, pos);
}
}
```