FHQ-Treap
简介
基于分裂与合并的 Treap。
基本操作
split
分裂
按权值分裂:
inline void split(int p, int k, int &x, int &y) {// p 表示当前分裂树的根节点,x,y 表示分裂成的两棵树的根节点,k 为关键字
if(! p) return x = y = 0, void();
if(val[p] <= k) {
x = p;
split(rs[p], k, rs[p], y);
}
else {
y = p;
split(ls[p], k, x, ls[p]);
}
psup(p);
}
按排名分裂:
inline void split(int p, int k, int &x, int &y) {
if(! p) return x = y = 0, void();
if(sz[ls[p]] >= k) {
y = p;
split(ls[p], k, x, ls[p]);
}
else {
x = p;
split(rs[p], k - sz[ls[p]] - 1, rs[p], y);
}
psup(p);
return ;
}
两种写法并非完全相同,需要看要求仔细斟酌。
其实就是跟权值相关还是跟编号相关的问题。
merge
合并
inline int merge(int x, int y) {
if(! x || ! y) return x | y;
if(rnd[x] > rnd[y]) { // 考虑 x,y 谁作为根,随机优先级大的作为根
rs[x] = merge(rs[x], y); // x 为根,将 x 原先的右子树和以 y 为根的子树合并作为新的右子树
psup(x);
return x;
}
else {
ls[y] = merge(x, ls[y]); // 反之,将 y 原先的左子树和以 x 为根的子树合并作为新的左子树
psup(y);
return y;
}
}
- 注:根据笔者的写法,相当于是左儿子写右边,右儿子写左边的,不过无伤大雅。
扩展操作
insert
插入
inline void insert(int p) {
int x = 0, y = 0;
split(root, p - 1, x, y);
merge(merge(x, create(p)), y);
}
相当于将新增节点当一颗新树合并进去。
delete
删除
inline void deleted(int p) {
int x = 0, y = 0, z = 0;
split(root, p, x, z);
split(x, p - 1, x, y);
y = merge(ls[y], rs[y]);
root = merge(merge(x, y), z);
return ;
}
分裂出只包含查询值
一定要注意合并的顺序!要按先前分裂的顺序倒序合并啊。
get_rank
查询排名
inline int get_rank(int k) {
int x = 0, y = 0, res = 0;
split(root, k - 1, x, y);
res = sz[x] + 1;
root = merge(x, y);
return res;
}
注意题目说明,用分裂后的信息去维护。
kth
第 小
inline int kth(int k) {
int p = root;
while(true) {
if(k <= sz[ls[p]]) p = ls[p];
else if(k == sz[ls[p]] + 1) return val[p];
else k -= sz[ls[p]] + 1, p = rs[p];
}
}
发现和编号相关,注意用的是 sz[]
。
序列操作
P2042 [NOI2005] 维护数列 ,经典例题。
维护序列时 Treap 就不需要满足 BST 性质了。保证中序遍历为原序列即可。
add
建树
对于原题中的原序列的建树和插入一段序列,需要在此序列上新建一颗平衡树去维护。
效仿线段树中的递归建树,可以得到此代码:
inline int add(int L, int R) {
if(L != R) {
int mid = (L + R) >> 1;
return merge(add(L, mid), add(mid + 1, R));
}
return create(a[L]);
}
相当与中序遍历维护上述性质。
懒标记
其实跟动态开点线段树差不多。
psup
标记上传
唯一可能和线段树有区别的地方了。
注意到平衡树的 BST 性质,根并不包含在左右儿子中,上传时算一下当前节点的贡献即可。
inline void psup(int p) {
sz[p] = sz[ls[p]] + sz[rs[p]] + 1;
sum[p] = sum[ls[p]] + sum[rs[p]] + val[p];
pre[p] = max({0, pre[ls[p]], sum[ls[p]] + pre[rs[p]] + val[p]});
suf[p] = max({0, suf[rs[p]], sum[rs[p]] + suf[ls[p]] + val[p]});
res[p] = max(0, suf[ls[p]] + pre[rs[p]]) + val[p];
if(ls[p]) res[p] = max(res[p], res[ls[p]]);
if(rs[p]) res[p] = max(res[p], res[rs[p]]);
return ;
}
各类修改函数
有区别吗?真的没有区别。
psd
下放标记
注意当前节点不存在时要 return
。
区间操作
把那一段区间分裂出来即可。
split(root, pos - 1, x, y);
split(y, tot, y, z);
注意合并顺序,按编号顺序合并即可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!