FHQ-Treap(非旋 Treap)学习笔记
本文由 Pretharp 编写,转载需注明出处,禁止用于任何形式的商业用途。
1 引入
1.1 算法相关
FHQ-Treap 是平衡树的一种,其称为非旋 Treap(即不用旋转的 Treap),又因该数据结构由范浩强发明,所以也被称为 FHQ-Treap。本平衡是笔者认为最好写也是最简单的一种,且其全面的功能可以替代除了 Splay 以外大部分平衡树,如果不理解 Treap 中抽象的 zig 和 zag,可以先学学 FHQ-Treap。但是本篇是建立于读者有一定平衡树基础的情况下而写,如果读者完全不知道平衡树是什么可以先读读 Treap 除了 zig 和 zag 的部分。
2 算法讲解
2.1 基本性质
Treap 是 Tree(树)和 Heap(堆)的组合词汇,因此,这种数据结构有着堆的性质:对于任何一个结点,其权值大于其左儿子(若有)的权值,小于其右儿子(若有)的权值。
除此之外,你需要维护以下东西:
- 每个结点的权值(\(val\)),子树大小(\(siz\)),左右儿子(\(ls \& rs\))编号以及其分配的一个随机权值(\(wei\))。
2.2 分裂(split)
这是 FHQ-Treap 的两个核心操作之一。分裂,就是以一个键值将平衡树分裂为两颗,通常情况下分裂后一颗子树的权值小于键值,另一颗则大于等于键值。操作具体如下:
- 若当前结点权值小于键值,则继续分裂其右儿子,将其右儿子小于键值的一部分作为该结点的新右儿子,反之亦然。
- 重复上述操作,直到当前子树为空。
代码如下:
pii split(int x, int key) {
if(!x) {
return {0, 0};
}
if(tr[x].val < key) {
pii t = split(tr[x].rs, key);
tr[x].rs = t.fir, update(x); // update 用于更新结点子树大小。
return {x, t.sec};
} else {
pii t = split(tr[x].ls, key);
tr[x].ls = t.sec, update(x);
return {t.fir, x};
}
}
2.3 合并(merge)
另一个核心操作。为了保证树的深度一定,我们将会以每个结点的随机权值合并,代码如下:
int merge(int x, int y) {
if(!x || !y) {
return x + y;
}
if(tr[x].wei < tr[y].wei) {
tr[x].rs = merge(tr[x].rs, y), update(x);
return x;
} else {
tr[y].ls = merge(x, tr[y].ls), update(y);
return y;
}
}
2.4 插入(insert)
新建一个结点,初始化即可,代码如下:
void insert(int x) {
++ idx;
tr[idx].siz = 1, tr[idx].val = x, tr[idx].wei = getRand(); // getRand() 用于得到一个随机数。
pii t = split(root, x);
/* return */ root = merge(merge(t.fir, idx), t.sec);
}
2.5 删除(erase)
我们可以以要删除的值为键值,分裂出两颗子树,再将大于键值的子树以键值加一分裂,这样就分裂出来了三颗子树,其中权值分别小于键值、等于键值、大于键值。我们将等于键值的子树的左右儿子合并,再依次合并回来就达到了删除的效果。代码如下:
void erase(int x) {
pii t1 = split(root, x), t2 = split(t1.sec, x + 1);
t2.fir = merge(tr[t2.fir].ls, tr[t2.fir].rs);
root = merge(t1.fir, merge(t2.fir, t2.sec));
}
2.6 通过权值查找排名(getRank)
以要找的权值作为键值分裂,小于键值的子树大小加一就是答案,代码如下:
int getRank(int x) {
pii t = split(root, x);
int res = tr[t.fir].siz + 1;
root = merge(t.fir, t.sec);
return res;
}
2.7 通过排名找权值(getVal)
类似于 Treap 的查找方式,流程如下:
- 若当前结点左子树大小大于等于排名,则递归左子树。
- 若当前结点左子树大小加一小于(也就是加上根节点的大小)排名,则在右子树中寻找第 \(rank-siz(lson)-1\) 大的结点。
- 若当前结点左子树大小加一等于排名,则答案就是根节点。
考虑到循环写法也很好写,所以采用了常数较小的循环写法,代码如下:
int getVal(int x) {
int p = root;
while(p) {
if(tr[tr[p].ls].siz + 1 == x) {
return tr[p].val;
} else if(tr[tr[p].ls].siz >= x) {
p = tr[p].ls;
} else {
x -= tr[tr[p].ls].siz + 1;
p = tr[p].rs;
}
}
}
2.8 查找前驱(lstVal)
比要找的权值的排名少一的数就是答案,代码如下:
int lstVal(int x) {
return getVal(getRank(x) - 1);
}
2.9 查找后继(nxtVal)
考虑带需要当前值可能不止一个,按照查找前驱的方法找到的返回值可能等同于自己。不过,如果我们要寻找 \(x+1\),这个值可能不存在,所以我们找到的就会是第一个大于等于 \(x+1\) 的值,也就是后继。代码如下:
int nxtVal(int x) {
return getVal(getRank(x + 1));
}
3 例题
3.1 例题1
例题 1:[洛谷 P6136] 【模板】普通平衡树(数据加强版)
题目大意:
你需要维护一个数据结构满足本文上述操作。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define fir first
#define sec second
const int N = 2e6 + 5;
int n, m;
namespace FHQTreap {
#define ttFHQT FHQTreap
const int S = (1e4 + 5e4 + 1919810) * 233 + 19;
struct FHQTreap {
private:
struct Tree {
int val, wei, ls, rs, siz;
} tr[N];
int idx, root = 0, seed = 1;
void update(int x) {
tr[x].siz = tr[tr[x].ls].siz + tr[tr[x].rs].siz + 1;
}
int merge(int x, int y) {
if(!x || !y) {
return x + y;
}
if(tr[x].wei < tr[y].wei) {
tr[x].rs = merge(tr[x].rs, y), update(x);
return x;
} else {
tr[y].ls = merge(x, tr[y].ls), update(y);
return y;
}
}
pii split(int x, int key) {
if(!x) {
return {0, 0};
}
if(tr[x].val < key) {
pii t = split(tr[x].rs, key);
tr[x].rs = t.fir, update(x);
return {x, t.sec};
} else {
pii t = split(tr[x].ls, key);
tr[x].ls = t.sec, update(x);
return {t.fir, x};
}
}
int getRand() {
return seed *= S;
}
public:
void insert(int x) {
idx ++;
tr[idx].val = x, tr[idx].siz = 1, tr[idx].wei = getRand();
pii t = split(root, x);
root = merge(merge(t.fir, idx), t.sec);
}
void erase(int x) {
pii t1 = split(root, x), t2 = split(t1.sec, x + 1);
t2.fir = merge(tr[t2.fir].ls, tr[t2.fir].rs);
root = merge(t1.fir, merge(t2.fir, t2.sec));
}
int getRank(int x) {
pii t = split(root, x);
int res = tr[t.fir].siz + 1;
root = merge(t.fir, t.sec);
return res;
}
int getVal(int x) {
int p = root;
while(p) {
if(tr[tr[p].ls].siz + 1 == x) {
return tr[p].val;
} else if(tr[tr[p].ls].siz >= x) {
p = tr[p].ls;
} else {
x -= tr[tr[p].ls].siz + 1;
p = tr[p].rs;
}
}
}
int lstVal(int x) {
return getVal(getRank(x) - 1);
}
int nxtVal(int x) {
return getVal(getRank(x + 1));
}
};
}
ttFHQT::ttFHQT fhq;
signed main() {
ios_base::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1, x; i <= n; i ++) {
cin >> x, fhq.insert(x);
}
int ans = 0, lst = 0;
while(m --) {
int op, x;
cin >> op >> x;
x ^= lst;
if(op == 1) {
fhq.insert(x);
} else if(op == 2) {
fhq.erase(x);
} else if(op == 3) {
ans ^= (lst = fhq.getRank(x));
} else if(op == 4) {
ans ^= (lst = fhq.getVal(x));
} else if(op == 5) {
ans ^= (lst = fhq.lstVal(x));
} else {
ans ^= (lst = fhq.nxtVal(x));
}
}
return cout << ans << endl, 0;
}

浙公网安备 33010602011771号