FHQ-Treap
一下简称
所以对于每个节点的值的大小,都满足左子树比他小,右子树比他大(或者一样大),而除了这个节点本身的值,我们还要给他赋一个随机权,作为小根堆的排名标准。
先来讲如何分裂。
定义结构体
struct bal
{
int a, b;
bal(int aa = 0, int bb = 0)
{
a = aa; b = bb;
}
};
void pushup(int x) { siz[x] = siz[son[x][0]] + siz[son[x][1]] + 1; }
bal split(int u, int x)
{
if (!u) return bal(0, 0);
if (key[u] < x)
{
bal t = split(son[u][1], x);
son[u][1] = t.a;
pushup(u);
return bal(u, t.b);
}
else
{
bal t = split(son[u][0], x);
son[u][0] = t.b;
pushup(u);
return bal(t.a, u);
}
}
另外一个核心操作是
int merge(int u, int v)
{
if (!u || !v) return u + v;
if (wei[u] < wei[v])
{
son[u][1] = merge(son[u][1], v);
pushup(u);
return u;
}
else
{
son[v][0] = merge(u, son[v][0]);
pushup(v);
return v;
}
}
此外,以上两个操作都不要忘记判断边界情况。
接下来是插入某个值。
为了满足
void insert(int x)
{
cnt ++; key[cnt] = x; wei[cnt] = rand1(), siz[cnt] = 1;
bal t = split(root, x);
root = merge(merge(t.a, cnt), t.b);
}
下面是删除某个数x。
我们希望有一个树的根的值是这个需要删除的值,然后直接合并他的左右儿子。我们还是考虑分裂,将原树分为
void erase(int x)
{
bal wtz = split(root, x);
bal gyx = split(wtz.b, x + 1);
gyx.a = merge(son[gyx.a][0], son[gyx.a][1]);
root = merge(wtz.a, merge(gyx.a, gyx.b));
}
接下来是根据值查找数
这很简单,先把原树分为小于
int find1(int x)
{
bal wtz = split(root, x);
int res = siz[wtz.a] + 1;
root = merge(wtz.a, wtz.b);
return res;
}
再来是根据排名
首先当前位置
否则如果
如果都不是,说明这个值在右子树中,将
int find2(int x)
{
int pos = root;
while (1)
{
if (siz[son[pos][0]] + 1 == x) return key[pos];
if (siz[son[pos][0]] >= x) pos = son[pos][0];
else x -= siz[son[pos][0]] + 1, pos = son[pos][1];
}
}
下面两个操作是查询前驱和后继。
其实只要理解了按照排名找值和按照值找排名,这两个操作会变得非常简单。
前驱就是排名比这个数的排名小
这两个操作都是先按值找排名,再按这个排名去找值就行了。
int lst(int x) { return find2(find1(x) - 1); }
int nxt(int x) { return find2(find1(x + 1)); }
这里补充一下随机权(即按照这个权值维护小根堆)的用处。
所谓平衡树,就是让这个树的高度是平衡的,因为合并和分裂是递归式的,所以如果树高为
下面是模板题总代码。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
struct bal
{
int a, b;
bal(int aa = 0, int bb = 0)
{
a = aa; b = bb;
}
};
int key[N], wei[N], son[N][2], cnt, seed = 1, siz[N], root;
int rand1() { return seed *= 19260817; }
void pushup(int x) { siz[x] = siz[son[x][0]] + siz[son[x][1]] + 1; }
bal split(int u, int x)
{
if (!u) return bal(0, 0);
if (key[u] < x)
{
bal t = split(son[u][1], x);
son[u][1] = t.a;
pushup(u);
return bal(u, t.b);
}
else
{
bal t = split(son[u][0], x);
son[u][0] = t.b;
pushup(u);
return bal(t.a, u);
}
}
int merge(int u, int v)
{
if (!u || !v) return u + v;
if (wei[u] < wei[v])
{
son[u][1] = merge(son[u][1], v);
pushup(u);
return u;
}
else
{
son[v][0] = merge(u, son[v][0]);
pushup(v);
return v;
}
}
void insert(int x)
{
cnt ++; key[cnt] = x; wei[cnt] = rand1(), siz[cnt] = 1;
bal t = split(root, x);
root = merge(merge(t.a, cnt), t.b);
}
void erase(int x)
{
bal wtz = split(root, x);
bal gyx = split(wtz.b, x + 1);
gyx.a = merge(son[gyx.a][0], son[gyx.a][1]);
root = merge(wtz.a, merge(gyx.a, gyx.b));
}
int find1(int x)
{
bal wtz = split(root, x);
int res = siz[wtz.a] + 1;
root = merge(wtz.a, wtz.b);
return res;
}
int find2(int x)
{
int pos = root;
while (1)
{
if (siz[son[pos][0]] + 1 == x) return key[pos];
if (siz[son[pos][0]] >= x) pos = son[pos][0];
else x -= siz[son[pos][0]] + 1, pos = son[pos][1];
}
}
int lst(int x) { return find2(find1(x) - 1); }
int nxt(int x) { return find2(find1(x + 1)); }
int main()
{
cin >> n;
int op, x;
for (int i = 1; i <= n; i ++)
{
scanf("%d %d", &op, &x);
if (op == 1) insert(x);
else if (op == 2) erase(x);
else if (op == 3) printf("%d\n", find1(x));//val->rank
else if (op == 4) printf("%d\n", find2(x));//rank->val
else if (op == 5) printf("%d\n", lst(x));
else if (op == 6) printf("%d\n", nxt(x));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效