F H Q T r e a p FHQ\ Treap FHQ Treap又称无旋 T r e a p Treap Treap ,由范浩强发明。他抛弃了旋转操作,使用分裂与合并两个操作来维护树的平衡。
T r e a p = t r e e + h e a p Treap = tree + heap Treap=tree+heap ,但竞赛中 s p l a y splay splay 更常用。
平衡树上的每个节点放 两个值:树的权值 val
和堆的随机值 key
,对于 val
值,维护查找树的性质,对于key
值,维护堆的性质。这样,即使顺序插入一个有序序列,也不会退化为一条链。堆的随机值等价于随机打乱了有序序列插入的顺序。
struct Node {
int l, r; //左右儿子
int key; //树的权值
int val; //堆的随机值
int size; //子树大小
}tr[N];
1、创建新节点 newnode
2、更新树的大小 pushup
3、分裂 split
O ( l o g n ) O(logn) O(logn)
按值分裂:根据给的值 v v v 把―棵树分裂成两棵树,—棵树的值 v a l ≤ v val ≤ v val≤v,另一棵树的值 v a l > v val > v val>v 。
如果当前节点 p
的 val <= v
,说明 p
以及其左子树都属于分裂后的左 T r e a p Treap Treap 。p
的右子树也可能部分 $≤ $ v
,因此需要继续递归分裂右子树,把 ≤ \le ≤ v
的那部分作为 p
的右子树。把 x
指向左 T r e a p Treap Treap 的根。
如果当前节点 p
的 val
> > > v
,说明 p
以及其右子树都属于分裂后的右 T r e a p Treap Treap 。p
的左子树也可能部分 > > > v
,因此需要继续递归分裂左子树,把 > > > v
的那部分作为 p
的左子树。把 y
指向右 T r e a p Treap Treap 的根。
4、合并 merge
O ( l o g n ) O(logn) O(logn)
x
、右 T r e a p Treap Treap 的根指针 y
。必须满足 x
中所有结点的 val
值小于等于 y
中所有结点的 val
值。key
值小的放在上面。5、插入 insert
6、删除 del
7、查询排名为 k 的节点 get_k
8、前驱和后继
9、查询数值 v 的排名
/*
1、构造新节点。
v:创建的新节点的权值
返回值:创建节点的编号
*/
int newnode(int v) {
tr[++ idx].val = v;
tr[idx].key = rand();
tr[idx].size = 1;
return idx;
}
/*
2、向上更新父亲节点的树的大小。
*/
void pushup(int u) {
tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + 1;
}
/*
3、分裂以u为树根的树,x指向左根,y指向右根。
u:当前节点
v:传入的数值
x:指向左Treap的根
y:指向右Treap的根
*/
void split(int u, int v, int &x, int &y) {
if (!u) {
x = 0, y = 0;
return;
}
if (tr[u].val <= v) {
x = u;
split(tr[u].r, v, tr[u].r, y);
} else {
y = u;
split(tr[u].l, v, x, tr[u].l);
}
pushup(u);
}
/*
4、将以x为左根,y为右根的两棵树合并成一棵。
x 左Treap的根
y 右Treap的根
返回值:合并之后的新树根
*/
int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].key <= tr[y].key) {
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
} else {
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
/*
5、插入一个权值为 v 的点。
*/
void insert(int v) {
int x, y, z;
split(root, v, x, y);
z = newnode(v);
root = merge(merge(x, z), y);
}
/*
6、删除权值为 v 的点。
*/
void del(int v) {
int x, y, z;
split(root, v, x, z);
split(x, v - 1, x, y);
y = merge(tr[y].l, tr[y].r);
root = merge(merge(x, y), z);
}
/**
7、返回第 k 个节点。
p 当前节点编号
k 返回第k个节点
返回值:第k个节点的编号。
*/
int get_k(int p, int k) {
if (k <= tr[tr[p].l].size)
return get_k(tr[p].l, k);
if (k == tr[tr[p].l].size + 1)
return p;
return get_k(tr[p].r, k - tr[tr[p].l].size + 1);
}
/*
9、找到 v 的前驱的值。
*/
void get_pre(int v) {
int x, y;
split(root, v - 1, x, y);
int p = get_k(x, tr[x].size());
printf("%d\n", tr[p].val);
root = merge(x, y);
}
/*
9、找到 v 的后继的值。
*/
void get_suc(int v) {
int x, y;
split(root, v, x, y);
int p = get_k(y, 1);
printf("%d\n", tr[p].val);
root = merge(x, y);
}
/*
获取权值为 v 的点在树中的排名。
*/
void get_rank(int v) {
int x, y;
split(root, v - 1, x, y);
printf("%d\n", tr[x].size + 1);
root = merge(x. y);
}
/*
获取排名为 k 的点的权值。
*/
void get_val(int k) {
int p = get_k(root, k);
printf("%d\n", tr[p].val);
}
参考资料:
董晓算法