fhq treap

fhq treap 是一种不用旋转的平衡树,它基于split(分裂)和merge(合并)来完成平衡树操作。
(感觉好像treap由普通二叉堆变成可并堆)

优缺点

fhq treap 难以充当 LCT 的辅助树,但是可以可持久化。

据说fhq treap的常数比Splay小(实际上要看运气,实测模板题平均比Splay慢),据说fhq treap的代码量比Splay小(实际上就差20行),但是fhq treap确实要比 Splay好理解很多,在用平衡树维护集合的时候最好用fhq treap。

实现

split(分裂)

fhq treap 的核心。

该操作是把一棵树用 \(k\) 砍成两半,一半全比 \(k\) 小(或等),一半全比 \(k\) 大。

void split(int cur, int k, int &x, int &y) {//val <= k
	if (!cur)	return x = y = 0, void();
	if (val[cur] <= k)	x = cur, split(rs[cur], k, rs[cur], y);//cur 属于x,去右边找 y
	else	y = cur, split(ls[cur], k, x, ls[cur]);//cur 属于 y,去左边找 x
	pushup(cur);
}

merge(合并)

类似线段树合并以及左偏树合并的操作。

int merge(int x, int y) {
	if (!x || !y)	return x ^ y;
	int p = 0;
	if (rnd[x] > rnd[y])	rs[x] = merge(rs[x], y), p = x;//保证堆性质,以保证复杂度
      //这里其实直接 if (rand() > rand()) 也能保证复杂度。
	else	ls[y] = merge(x, ls[y]), p = y;
	pushup(p);
	return p;
}

其它

其它所有操作几乎都是对 split 和 merge 的巧妙运用,不过有些(get_new,get)则是treap原来的东西。

inline int get_new(int vl) {
	int cur = ++ttot;
	val[cur] = vl; siz[cur] = 1; ls[cur] = rs[cur] = 0;
	rnd[cur] = rand();
	return cur;
}
inline void ins(int x) {
	int cur = get_new(x);
	int A = 0, B = 0;
	split(root, x, A, B);
	root = merge(merge(A, cur), B);
}
inline void del(int x) {
	int A = 0, B = 0, C = 0;
	split(root, x, A, C);
	split(A, x - 1, A, B);
	B = merge(ls[B], rs[B]);
	root = merge(merge(A, B), C);
}
int get(int cur, int k) {//get k-th
	if (siz[ls[cur]] == k - 1)	return cur;
	if (siz[ls[cur]] > k - 1)	return get(ls[cur], k);
	else	return get(rs[cur], k - 1 - siz[ls[cur]]);
}
inline int get_rk(int x) {
	int A = 0, B = 0;
	split(root, x - 1, A, B);
	int res = siz[A] + 1;
	root = merge(A, B);
	return res;
}
inline int get_pre(int x) {
	int A = 0, B = 0;
	split(root, x - 1, A, B);
	int cur = get(A, siz[A]);
	int res = val[cur];
	root = merge(A, B);
	return res;
}
inline int get_nxt(int x) {
	int A = 0, B = 0;
	split(root, x, A, B);
	int cur = get(B, 1);
	int res = val[cur];
	root = merge(A, B);
	return res;
}

题目

P3369 【模板】普通平衡树

模板题,不再多说。

P3835 【模板】可持久化平衡树

可持久化是fhq treap比Splay强的一个方面。
可持久化的一个基本套路就是:不要修改之前节点的信息,想要修改先复制。这道题主要涉及到 \(ls,rs,siz\) 的修改。
注意我们有时候要利用0节点标号为0的特点,因此复制0节点的时候也要把标号复制一下,即不复制。
注意开够空间。

Code:

int v;//当前版本
int ls[NN], rs[NN], val[NN], rnd[NN], siz[NN], rt[N], ttot;
inline int cpy(int cur) {
	if (!cur)	return 0;
	int p = ++ttot;
	val[p] = val[cur], ls[p] = ls[cur], rs[p] = rs[cur], siz[p] = siz[cur];
	rnd[p] = rnd[cur];
	return p;
}
void split(int cur, int k, int &x, int &y) {
	if (!cur)	return x = y = 0, void();
	if (val[cur] <= k)
	 x = cpy(cur), split(rs[x], k, rs[x], y), pushup(x);
	else	y = cpy(cur), split(ls[y], k, x, ls[y]), pushup(y);
}
int merge(int x, int y) {
	if (!x || !y)	return x ^ y;
	int p;
	if (rnd[x] > rnd[y])	return p = cpy(x), rs[p] = merge(rs[p], y), pushup(p), p;
	return p = cpy(y), ls[p] = merge(x, ls[p]), pushup(p), p;
}

...
int main() {
      ...
      for (register int i = 1; i <= n; ++i) {
		...
		rt[i] = cpy(rt[v]);
		v = i;
		...
	}
      ...
}

P3987 我永远喜欢珂朵莉

这道题告诉我们 fhq treap 实际上也可以支持维护序列的操作。

灵活使用 split 和 merge 操作以及lazy tag可以解决大部分问题。

Code

曾经出过的错

错误1

int get(int cur, int k) {
	if (siz[ls[cur]] == k - 1)	return cur;
	if (k - 1 < siz[ls[cur]])	return get(ls[cur], k);
      //这里是 k-1 < siz[ls[cur]],不是 siz[ls[cur]] < k - 1!
	return get(rs[cur], k - siz[ls[cur]] - 1);
}
posted @ 2020-07-07 16:30  JiaZP  阅读(263)  评论(0编辑  收藏  举报