平衡树——(不旋转的那种)——fhp heap——可持久化序列
一、什么是fhp treap,有什么用
treap,即tree+heap,而且这个tree是BST
简单的讲,treap是一棵树,而它有以下几个性质
1、样子就是(或 几乎是)一颗完全二叉树


2、它的节点的值满足BST——即: 左儿子 <= 根 <= 右儿子
3、它的节点序号满足根堆,若令其满足大根堆——即: 根节点序号 > 左儿子、右儿子 ——而这里我们用一个随机的fix数组来维护(保证结构的随机性)
--------->fhp treap
若是有旋treap,则我们通过旋转树的方式来保证树的平衡,
而对于非旋treap,我们则通过 合并+拆分 来实现——那么,由于拆分和合并,所以每一颗子树内的顺序是不会变的,所以,我们可以拿来维护序列___
---------------------------->可持久化
由于我们是合并+拆分,那么整棵树的大致结构就不会变,由此可以延伸出 持久化 的作用——要变的建立新节点,添加更改过后的节点并记录,复制不变的节点——这样,我们既有了新的一个版本,也保留了原来的版本,于是达到了持久化的目的
那么来看一道题——
二、例题
#2782 可持久化序列【模板】
描述
您需要维护一个序列,其中需要提供以下操作:
插入一个数到序列的第 t 版本使其成为序列的第 k项,这个数为 x ;
删除序列的第 t 版本的第 k 项;
查询序列的第 t 版本的第 k 项。
第 0 个版本为空序列。修改操作不会影响被修改的版本,而总是产生一个新版本。
输入
第一行有一个正整数 n 表示操作的数量。
接下来 n 行每行第一个正整数 opt 表示操作的类型,后面有 3 个整数 t,k,x 或 2 个整数 t,k 表示操作的参数。
输出
对于每个查询操作输出一行一个数,表示查询的结果。
样例输入 17 1 0 1 1 1 1 1 2 1 2 1 3 2 3 2 3 4 2 1 4 3 4 1 5 1 5 3 3 2 1 3 4 6 1 6 3 7 1 7 1 8 3 8 2 3 7 3 2 8 4 2 9 2 3 11 4 3 10 4 样例输出 1 2 3 1 6 4
代码——
#include <bits/stdc++.h> using namespace std; #define lc(x) ch[x][0] #define rc(x) ch[x][1] #define N 300010 #define M (N * 50) typedef pair<int, int> dat; //代表分裂后两颗子树的根节点编号 int ch[M][2], val[M], siz[M], fix[M]; //val维护BST的性质,fix维护斜堆的性质 int cnt = 0, n, rt[N]; //rt维护的是第t个版本的平衡树的根节点 inline int read() { int f = 1, k = 0; char c = getchar(); while (c != '-' && (c < '0' || c > '9')) { c = getchar(); } if (c == '-') { f = -1; c = getchar(); } while (c >= '0' && c <= '9') { k = (k << 3) + (k << 1) + (c ^ 48); c = getchar(); } return f * k; } inline void copy(int a, int b) { //复制一遍节点 val[a] = val[b]; siz[a] = siz[b]; ch[a][0] = ch[b][0]; ch[a][1] = ch[b][1]; fix[a] = fix[b]; } inline void pushup(int o) { //和线段树的同理 siz[o] = siz[lc(o)] + siz[rc(o)] + 1; } inline int newnode(int x = 0) { //新建节点——持久化 val[++cnt] = x; siz[cnt] = 1; ch[cnt][0] = ch[cnt][1] = 0; fix[cnt] = rand() % 5000; return cnt; } inline int merge(int a, int b) { //合并:由于我们的维护,左右子树(a,b)已经满足了BST,然后我们根据 if (!a || !b) { //随机出来的fix来满足根节点的priority性质(决定谁做合并后的子树的根) return a + b; } int x = ++cnt; if (fix[a] < fix[b]) { copy(x, a); ch[x][1] = merge(ch[x][1], b); } else { copy(x, b); ch[x][0] = merge(a, ch[x][0]); } pushup(x); return x; } inline dat split(int o, int k) { //拆分将以o为节点的树分裂,k就相当于是拆分出来的左子树的大小, if (!o) { //返回的是两颗子树的根节点编号 return make_pair(0, 0); } //上一个结点已经是叶子结点 int x = ++cnt; //建立新节点 copy(x, o); dat t; if (k <= siz[lc(x)]) { t = split(lc(x), k); ch[x][0] = t.second; pushup(x); return make_pair(t.first, x); } else { t = split(rc(x), k - siz[lc(x)] - 1); ch[x][1] = t.first; pushup(x); return make_pair(x, t.second); } } inline int insert(int o, int k, int x) { dat t = split(o, k - 1); //先把前k-1分裂出来 int tmp = newnode(x); int ret = merge(merge(t.first, tmp), t.second); return ret; //返回新版本根节点编号 } inline int del(int o, int k) { dat a = split(o, k - 1); dat b = split(a.second, 1); return merge(a.first, b.second); } inline int f_k_th(int o, int k) { //找第k大 if (!o) return 0; if (k == siz[lc(o)] + 1) return val[o]; //当前子树根节点 if (k <= siz[lc(o)]) return f_k_th(lc(o), k); return f_k_th(rc(o), k - siz[lc(o)] - 1); } int main() { int tot = 0; rt[0] = 0; n = read(); for (int i = 1; i <= n; i++) { int op, t, k, x; op = read(); t = read(); k = read(); if (op == 1) x = read(); switch (op) { case 1: { rt[++tot] = insert(rt[t], k, x); break; } case 2: { rt[++tot] = del(rt[t], k); break; } case 3: { printf("%d\n", f_k_th(rt[t], k)); break; } } } return 0; }

浙公网安备 33010602011771号