浅学 FHQ
初学者千万不要被吓到,不要因为代码略长就害怕,打的代码越来越长才能增长码力。
以 模板题 为例。
维护信息
key
,也就是维护的序列或其他东西里的值。val
,Treap 带的用于平衡时间复杂度的随机参数。l,r
,左右儿子。siz
,节点的子树大小。
主要操作
合并
合并两颗合法的 Treap,且其中一颗里的 key
值全部大于另外一颗。
令前者的根为 \(y\),后者的根为 \(x\)。比较两者的 val
,大的一方作为父亲,小的一方作为儿子,以维持平衡,注意以 \(x\) 为根的树一定在左,所以 \(y\) 只能做右儿子,\(x\) 只能做左儿子。
假设以 \(x\) 为根,那么更新 \(x\) 的右儿子,再将 \(x\) 的右儿子和 \(y\) 合并即可,递归处理。\(y\) 同理。
分裂
以一个值 \(k\) 为界,小于等于 \(k\) 的放到一棵树里,大于 \(k\) 的放到一颗树里。
同样在递归过程中处理。比较当前的根节点 key
和 \(k\),如果 key
\(\le k\),那么左子树和根节点都应被切到左边,递归处理右子树。反之同理。稍微复杂的细节建议看代码。
更新
分裂合并后树的形态都会改变,所以要及时 pushup
,更新子树大小。
完成询问操作
插入
以插入值为界,分裂当前的平衡树,再按顺序合并左边,新建的节点,右边。
删除
先分裂成小于等于删除值的和大于的,再将前者以删除值 \(-1\) 为界分裂成小于和等于的。
如何删除等于的这颗树里的一个?只需要把这颗树的根的左右儿子合并即可,根自然就被删除了,然后把这三棵树重新按顺序合并即可。
查询排名
将树分裂成小于等于查询值 \(-1\) 的和大于查询值的,输出左边子树大小 \(+1\),重新合并。
查询第 \(k\)
若左子树 siz
\(\ge k\),递归左边,若左子树 siz
\(+1\) 等于 \(k\),返回根节点 key
,否则递归右子树。
查询前驱
将大于等于查询值的放到右边(就是前面的按查询值 \(-1\) 为界),然后用查询第 \(k\) 查询左子树里的最后一个(第 siz
个)。重新合并。
查询后继
同理,将小于等于查询值的放到左边,查询右子树的第一个。重新合并。
FHQ
充分发扬人类智慧,用两个基础操作实现了平衡树,也很好理解,强烈推荐大家学习。
最后看看板子题代码。
#include <bits/stdc++.h>
// #define int long long
#define x first
#define y second
using namespace std;
typedef pair <int, int> pii;
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-10, PI = acos (-1);
const int N = 2e5 + 10, M = 2e5 + 10;
//const int mod = 1e9 + 7;
//const int mod = 998244353;
int idx, root;
struct node
{
int val, key;
int l, r, siz;
} tr[N];
int get_node(int x)
{
tr[++idx] = {rand (), x, 0, 0, 1};
return idx;
}
void pushup(int u)
{
tr[u].siz = tr[tr[u].l].siz + tr[tr[u].r].siz + 1;
}
void split(int p, int &l, int &r, int k)//l,r 记录分裂后的根
{
if (!p) //空节点返回
{
l = r = 0;
return;
}
if (tr[p].key <= k) //把左子树扔到左边,处理右子树
{
l = p;
split (tr[p].r, tr[p].r, r, k);//要将根节点的右儿子处理为右儿子分裂出来的左边
}
else
{
r = p;
split (tr[p].l, l, tr[p].l, k);//同理
}
pushup (p);//更新
}
int merge(int x, int y)
{
if (!x || !y) return x | y;//有一个为空直接返回另一个
if (tr[x].val > tr[y].val)//x 为根
{
tr[x].r = merge (tr[x].r, y);
pushup (x);
return x;
}
else//y 为根
{
tr[y].l = merge (x, tr[y].l);
pushup (y);
return y;
}
}
void insert(int x)
{
int l, r;
split (root, l, r, x);
root = merge (merge (l, get_node (x)), r);
}
void del(int x)
{
int tep, bg, sm, eq;//bg:>, sm:<, eq:=.
split (root, tep, bg, x);
split (tep, sm, eq, x - 1);
eq = merge (tr[eq].l, tr[eq].r);
root = merge (merge (sm, eq), bg);
}
int get_rank(int x)
{
int l, r;
split (root, l, r, x - 1);
int ans = tr[l].siz + 1;
root = merge (l, r);
return ans;
}
int get_kth(int p, int k)
{
if (tr[tr[p].l].siz >= k) return get_kth (tr[p].l, k);
else if (tr[tr[p].l].siz + 1 == k) return tr[p].key;
else return get_kth (tr[p].r, k - tr[tr[p].l].siz - 1);
}
int get_prev(int x)
{
int l, r;
split (root, l, r, x - 1);
int ans = get_kth (l, tr[l].siz);
root = merge (l, r);
return ans;
}
int get_next(int x)
{
int l, r;
split (root, l, r, x);
int ans = get_kth (r, 1);
root = merge (l, r);
return ans;
}
int main()
{
// srand (time (0));
int n;
cin >> n;
while (n--)
{
int op, x;
cin >> op >> x;
if (op == 1) insert (x);
if (op == 2) del (x);
if (op == 3) cout << get_rank (x) << "\n";
if (op == 4) cout << get_kth (root, x) << "\n";
if (op == 5) cout << get_prev (x) << "\n";
if (op == 6) cout << get_next (x) << "\n";
}
return 0;
}
完结撒花 ★,°:.☆( ̄▽ ̄)/$:.°★ 。