可持久化线段树

· · 个人记录

可持久化线段树

壹、可持久化数据结构

定义:支持查询历史的数据状态的数据结构。
举例:可持久化并查集,可持久化线段树,可持久化 Trie

贰、可持久化线段树

解法:n 颗线段树(小心不要MLE)

  1. 建多颗线段树(每一颗线段树代表一次修改)
  2. 相邻两颗线段树之间差别很小,所以每颗线段树在物理上,只需要存储与前一颗线段树的不同之处,使用时填补并生成一颗完整的线段树。
  3. 任意量颗线段树相减可以得到一颗新的线段树,这颗线段树就是两次之间的改变,往往统计答案也是在这一颗线段树里面查询。

权值线段树必须要写离散化!

练习1:Count On A Tree

题意:给定一个带权树,求 uv 之间链上第 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); } } ```