FHQ-treap 学习笔记

介绍

平衡树

平衡树,又称treap,是树(tree)以及堆(heap)的合称,具体表现为形式上它是一棵二叉树,而性质上它又满足堆的性质。与普通的 BST(Binary Search Tree) 相比,它由于具有了堆的性质从而使二叉树平衡,避免了当数据单调时BST退化成链的劣势,将时间复杂度降到了均摊 O(logn) 的级别

FHQ-treap

FHQ-treap是一种无旋平衡树,拥有码量小、容易写的优点。具体通过 split 和 merge 两种操作实现,即将整棵树分裂为两棵树的操作与将两棵树合并为一棵完整的平衡树的,对节点进行操作时视情况将整棵平衡树按权值或按排名分裂,从而避免了普通treap里面繁琐的旋转操作。

核心操作

upd

功能

很简单,更新以 p 为根节点的树的大小信息,这个信息在与排名有关的操作中很有用

code

inline void upd(int p) {siz[p] = siz[lc[p]] + siz[rc[p]] + 1;}

split

功能

将根节点为 p 的树分裂为根节点为 lr 的两棵 treap,并且使树 l 上的所有权值都小于等于 v,树 r 上的所有权值都大于 v, 这两棵树仍然满足 treap 的性质。

code

void split(int p, int v, int &l, int &r) {
    if(!p) {l = r = 0; return;}
    if(val[p] <= v) l = p, split(rc[p], v, rc[p], r);
    else r = p, split(lc[p], v, l, lc[p]);
    upd(p);
}

理解

如图,如果从根也就是24按38分裂的话,调用的代码如下

split(rt, 38, x, y); 

因为 38>24 ,所以会将 24 暂时赋值给 x,但是由于BST的性质,39 的左儿子小于 39,也可能小于 v ,在这个例子中也就是 33,所以将 rcp 作为参数 l 传进下一层递归中,因为 lr 是引用的,所以可以将 39 的左儿子赋值给 rcp ,这样的话就能使分裂后的两棵树满足BST的性质。

这里给的是按权值分裂的代码,如果要实现按排名分裂,就需要判断 sizlcp 与排名 k 的关系了


merge

功能

可以理解为 split 的逆运算,将根节点为 lr 的两棵树合并并且根据 lr 的随机权值将这棵树的根节点赋为 lr

code

inline int merge(int l, int r) {
	if(!l || !r) return l + r;
	if(rd[l] <= rd[r]) {
		lc[r] = merge(l, lc[r]);
		upd(r); return r;
	}
	else {
		rc[l] = merge(rc[l], r);
		upd(l); return l;
	}
}

理解

我们在记忆或者说理解 splitmerge 的函数操作时只需要谨记一点:BST的性质,即左子树上的权值永远小于根节点,右子树上的权值永远大于根节点

merge 这个函数的操作我就不单独说了


ins

code

inline void rand(int &xx) {xx = (rnd = (rnd * 73819) % 23333);}
inline void cre(int v) {
	rand(rd[++ cnt]);
	val[cnt] = v;
	siz[cnt] = 1;
}
inline void ins(int v) {
	split(rt, v, x, y);
	cre(v);
	rt = merge(merge(x, cnt), y);
}

理解

cre 以及 rand 没啥好说的,新建一个节点,节点总数 +1 ,给这个节点赋一个随机值,更新权值以及这个节点的大小。

需要注意 cre 的功能仅仅为新建一个节点的信息,执行完以后这个节点独立于整棵树之外,在 insert 函数中才会将它插入到整棵树当中


例题

P3369 【模板】普通平衡树

Description

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

1.插入 x

2.删除 x 数(若有多个相同的数,因只删除一个)

3.查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )

4.查询排名为 x 的数

5.求 x 的前驱(前驱定义为小于 x,且最大的数)

6.求 x 的后继(后继定义为大于 x,且最小的数)

Solution

平衡树板题,关于排名的一切操作都用到 siz 就好了

Code

#include<iostream>
#include<stdio.h>
#define maxn 100005
using namespace std;
int lc[maxn], rc[maxn], siz[maxn], val[maxn], rd[maxn];
int rnd = 1978347, x, y, z, rt, cnt;
inline void rand(int &xx) {xx = (rnd = (rnd * 73819) % 23333);}
inline void cre(int v) {
	rand(rd[++ cnt]);
	val[cnt] = v;
	siz[cnt] = 1;
}
inline void upd(int p) {siz[p] = siz[lc[p]] + siz[rc[p]] + 1;}
inline void split(int p, int v, int &l, int &r) {
	if(!p) {l = r = 0; return;}
	if(val[p] <= v) l = p, split(rc[p], v, rc[p], r);
	else r = p, split(lc[p], v, l, lc[p]);
	upd(p);
}
inline int merge(int l, int r) {
	if(!l || !r) return l + r;
	if(rd[l] <= rd[r]) {
		lc[r] = merge(l, lc[r]);
		upd(r); return r;
	}
	else {
		rc[l] = merge(rc[l], r);
		upd(l); return l;
	}
}
inline void ins(int v) {
	split(rt, v, x, y);
	cre(v);
	rt = merge(merge(x, cnt), y);
}
inline void del(int v) {
	split(rt, v, x, y);
	split(x, v - 1, x, z);
	z = merge(lc[z], rc[z]);
	rt = merge(merge(x, z), y);
}
inline int findrank(int v) {
	split(rt, v - 1, x, y);
	int tmp = siz[x] + 1;
	rt = merge(x, y);
	return tmp;
}
inline int kth(int k) {
	int p = rt;
	while(p) {
		if(siz[lc[p]] + 1 == k) break;
		else if(siz[lc[p]] + 1 >= k) p = lc[p];
		else k -= siz[lc[p]] + 1, p = rc[p]; 
	}
	return val[p];
}
inline int front(int v){
	split(rt, v - 1, x, y);
	int p = x;
	while(rc[p]) p = rc[p];
	rt = merge(x, y);
	return val[p];
}
inline int back(int v){
	split(rt, v, x, y);
	int p = y;
	while(lc[p]) p = lc[p];
	rt = merge(x, y);
	return val[p];
}
int main(){
	int n, opt, xx;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) {
		scanf("%d%d", &opt, &xx);
		if(opt == 1) ins(xx);
		else if(opt == 2) del(xx);
		else if(opt == 3) printf("%d\n", findrank(xx));
		else if(opt == 4) printf("%d\n", kth(xx));
		else if(opt == 5) printf("%d\n", front(xx));
		else printf("%d\n", back(xx));
	}
	return 0;
} 


P1503 鬼子进村

Description

n 个房子,排成一排,第 i 个房子只与第 i1i+1 个相连,下面有 m 个信息:

1.若消息为 D x:鬼子将 x 号房子摧毁了,地道被堵上。

2.若消息为 R :村民们将鬼子上一个摧毁的房子修复了。

3.若消息为 Q x:有一名士兵被围堵在 x 号房子中,输出他能到达的房子数量。

Solution

1.若消息为 D x,将 x 插入平衡树。

2.若消息为 R ,将上一个 x 删除。

3.若消息为 Q x,根据 x 的前驱和后继求能到达的房子树

注意,在开始将 0n+1 就插入平衡树

Code

#include<iostream>
#include<cstdio>
#include<stack>
#define maxn 50004
using namespace std;
int lc[maxn], rc[maxn], val[maxn], siz[maxn], rd[maxn], rnd = 97231, cnt, rt, x, y, z;
bool des[maxn];
inline void rand(int &x) {x = (rnd = (rnd * 233531) % 1000000);}
inline void cre(int v) {
	rand(rd[++ cnt]);
	val[cnt] = v;
	siz[cnt] = 1;
}
inline void upd(int p) {siz[p] = siz[lc[p]] + siz[rc[p]] + 1;}
inline void split(int p, int v, int &l, int &r) {
	if(!p) {l = r = 0; return;}
	if(v >= val[p]) l = p, split(rc[p], v, rc[p], r); 
	else r = p, split(lc[p], v, l, lc[p]);
	upd(p);
}
inline int merge(int l, int r) {
	if(!l || !r) return l + r;
	if(rd[l] <= rd[r]) {
		lc[r] = merge(l, lc[r]); 
		upd(r); return r;
	}
	else {
		rc[l] = merge(rc[l], r);
		upd(l); return l;
	}
}
inline void ins(int v) {
	split(rt, v, x, y);
	cre(v);
	rt = merge(merge(x, cnt), y);
}
inline void del(int v) {
	split(rt, v - 1, x , y);
	split(y, v, y, z);
	y = merge(lc[y], rc[y]);
	rt = merge(merge(x, y), z);
}
inline int front(int v) {
	split(rt, v - 1, x, y);
	int p = x;
	while(rc[p]) p = rc[p];
	rt = merge(x, y);
	return val[p];
}
inline int back(int v) {
	split(rt, v, x, y);
	int p = y;
//	cout << lc[p] << "n\n";
	while(lc[p]) p = lc[p];
	rt = merge(x, y);
	return val[p];
}
stack<int> st;
int n, m, q;
char tmp;
int main() {
	ios :: sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> m;
	ins(0);
	ins(n + 1);
	for(int i = 1; i <= m; ++ i) {
		cin >> tmp;
		if(tmp == 'D') cin >> q, ins(q), st.push(q), des[q] = 1;
		else if(tmp == 'Q'){
			cin >> q; 
			if(des[q]) printf("0\n"); 
			else printf("%d\n", back(q) - front(q) - 1);
		}
		else del(st.top()), des[st.top()] = 0, st.pop();
	}
	return 0;
}
posted @   8ovehat1r  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示