【10.13】FHQ-Treap
FHQ-Treap
一种无需旋转,只使用分裂、合并来维护一个Treap的方式。
参考blog:
- https://www.cnblogs.com/Judge/p/9506980.html
- https://www.cnblogs.com/TimelessWelkinBlog/p/17610065.html
原理
无旋Treap中每个点有真实值val
和随机权值key
,
val
保证Treap的平衡树性质(也就是每个节点左子树内节点真实权值小于它,右子树相反),
key
满足大(小)根堆的性质,以其随机性维护树的平衡,使得期望树高在 \(logn\) 级别。
结构体/变量定义
std::mt19937 Rand(9023645);
struct D{
int ch[2],val,key,size;
void new_(int x){
ch[0]=ch[1]=0; val=x; key=Rand(); size=1;
return;
}
}t[MAXN];
int root,cnt;
void pushup(int now){
t[now].size=t[t[now].ch[0]].size+t[t[now].ch[1]].size+1;
return;
}
重要操作
- Merge(合并)
假设有两颗子树u,v,且 u 的所有节点的值都小于 v 的所有节点的值,随机权值 key 都以小根堆的形式存储。
int merge(int u,int v){
if (!u || !v) return u|v; //空的直接返回
if (t[u].key<t[v].key){ //此时u节点一定要放在v节点上面,所以将u的右子树与v的子树继续合并,合并后的根即为u的右儿子
t[u].ch[1]=merge(t[u].ch[1],v);
pushup(u); return u;
} else { //反之同理
t[v].ch[0]=merge(u,t[v].ch[0]);
pushup(v); return v;
}
}
- Split(分裂)
- 按值split:
首先得有个基准值 x ,即权值小于等于 x 的节点全部进入左树,大于x的节点全部进入右树。
void split_val(int now,int x,int &u,int &v){
if (!now) return (void)(u=v=0); //空树则返回空指针
if (t[now].val<=x){ //节点值小于基准值,则他本身和他的左子树全部进入左树,右子树继续分裂,其分裂后小于基准值的部分成为这个节点的右子树,大于基准值的部分进入右树
u=now; split_val(t[now].ch[1],x,t[now].ch[1],v);
} else { //反之亦然
v=now; split_val(t[now].ch[0],x,u,t[now].ch[0]);
}
pushup(now); //记得pushup
return;
}
- 按排名split:
就是把前 rk 个节点拆入左树,其它节点拆入右树。原理与按值分裂差不多。
void split_rk(int now,int rk,int &u,int &v){
if (!now) return (void)(u=v=0);
if (t[t[now].ch[0]].size<rk){ //左子树的大小小于rk时,节点和左子树进入左树,右子树继续分裂
u=now; split_rk(t[now].ch[1],rk-t[t[now].ch[0]].size-1,t[now].ch[1],v);
} else {
v=now; split_rk(t[now].ch[0],rk,u,t[now].ch[0]);
}
pushup(now);
return;
}
其他操作
- 插入
插入权值为 x 的节点时,先新建一个节点,再以 x 为界按权值 split 整棵树为a,b,再按顺序 merge a,x,b。
void insert(int x){
int a,b;
split_val(root,x,a,b);
t[++cnt].new_(x);
root=merge(merge(a,cnt),b);
return;
}
- 删除
要删除x,先将整棵树以 x-1 为界按权值split 成a和b,再将 b 以 1 为界 按排名split 成c和d,则 c 就是要删除的节点。最后按顺序merge a,b,d。(如果x不存在则c的值不是x)
void del(int x){
int a,b,c,d;
split_val(root,x-1,a,b);
split_rk(b,1,c,d);
if (t[c].val==x) root=merge(a,d);
else root=merge(merge(a,c),d);
return;
}
- 查询x的排名
先将整棵树以x-1按权值split成a和b,则a的siz+1即为x的排名。
int get_rk(int x){
int a,b; int res;
split_val(root,x-1,a,b);
res=t[a].size+1;
root=merge(a,b);
return res;
}
- 查询排名为k的值
先split出整棵树前k-1小节点,则右树最小节点即为所求节点,再次split 即可。
int to_be_rk(int rk){
int a,b,c,d; int res;
split_rk(root,rk-1,a,b);
split_rk(b,1,c,d);
res=t[c].val;
root=merge(a,merge(c,d));
return res;
}
- 查x前驱
将整棵树以x-1按权值split,左树中最大节点即为所求节点。
int pre(int x){
int a,b,c,d; int res;
split_val(root,x-1,a,b);
split_rk(a,t[a].size-1,c,d);
res=t[d].val;
root=merge(merge(c,d),b);
return res;
}
- 查x后继
将整棵树以x按权值split,右树中最小节点即为所求节点。
int suf(int x){
int a,b,c,d; int res;
split_val(root,x,a,b);
split_rk(b,1,c,d);
res=t[c].val;
root=merge(a,merge(c,d));
return res;
}
例题
-
P3369【模板】普通平衡树
您需要写一种数据结构,来维护一些数,其中需要提供以下操作:
- 插入一个数 \(x\)。
- 删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
- 定义排名为比当前数小的数的个数 \(+1\)。查询 \(x\) 的排名。
- 查询数据结构中排名为 \(x\) 的数。
- 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
- 求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。
对于操作 3,5,6,不保证当前数据结构中存在数 \(x\)。
\(1\le n \le 10^5\),\(|x| \le 10^7\)
Code
#include<bits/stdc++.h>
#define MAXN 100003
using namespace std;
int n;
std::mt19937 Rand(9023645);
struct D{
int ch[2],val,key,size;
void new_(int x){
ch[0]=ch[1]=0; val=x; key=Rand(); size=1;
return;
}
}t[MAXN];
int root,cnt;
void pushup(int now){
t[now].size=t[t[now].ch[0]].size+t[t[now].ch[1]].size+1;
return;
}
int merge(int u,int v){
if (!u || !v) return u|v;
if (t[u].key<t[v].key){
t[u].ch[1]=merge(t[u].ch[1],v);
pushup(u); return u;
} else {
t[v].ch[0]=merge(u,t[v].ch[0]);
pushup(v); return v;
}
}
void split_val(int now,int x,int &u,int &v){
if (!now) return (void)(u=v=0);
if (t[now].val<=x){
u=now; split_val(t[now].ch[1],x,t[now].ch[1],v);
} else {
v=now; split_val(t[now].ch[0],x,u,t[now].ch[0]);
}
pushup(now);
return;
}
void split_rk(int now,int rk,int &u,int &v){
if (!now) return (void)(u=v=0);
if (t[t[now].ch[0]].size<rk){
u=now; split_rk(t[now].ch[1],rk-t[t[now].ch[0]].size-1,t[now].ch[1],v);
} else {
v=now; split_rk(t[now].ch[0],rk,u,t[now].ch[0]);
}
pushup(now);
return;
}
void insert(int x){
int a,b;
split_val(root,x,a,b);
t[++cnt].new_(x);
root=merge(merge(a,cnt),b);
return;
}
void del(int x){
int a,b,c,d;
split_val(root,x-1,a,b);
split_rk(b,1,c,d);
if (t[c].val==x) root=merge(a,d);
else root=merge(merge(a,c),d);
return;
}
int get_rk(int x){
int a,b; int res;
split_val(root,x-1,a,b);
res=t[a].size+1;
root=merge(a,b);
return res;
}
int to_be_rk(int rk){
int a,b,c,d; int res;
split_rk(root,rk-1,a,b);
split_rk(b,1,c,d);
res=t[c].val;
root=merge(a,merge(c,d));
return res;
}
int pre(int x){
int a,b,c,d; int res;
split_val(root,x-1,a,b);
split_rk(a,t[a].size-1,c,d);
res=t[d].val;
root=merge(merge(c,d),b);
return res;
}
int suf(int x){
int a,b,c,d; int res;
split_val(root,x,a,b);
split_rk(b,1,c,d);
res=t[c].val;
root=merge(a,merge(c,d));
return res;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for (int i=1;i<=n;i++){
int type,x; cin>>type>>x;
if (type==1){
insert(x);
} else if (type==2){
del(x);
} else if (type==3){
cout<<get_rk(x)<<endl;
} else if (type==4){
cout<<to_be_rk(x)<<endl;
} else if (type==5){
cout<<pre(x)<<endl;
} else {
cout<<suf(x)<<endl;
}
}
return 0;
}
-
P3960 [NOIP2017 提高组] 列队
Solu
注意到区间值的连续性,考虑使用平衡树维护区间,支持分裂区间,快速加点、删点操作。
Code
常数过大,未AC
#include <bits/stdc++.h>
#define MAXN 300003
#define int long long
using namespace std;
int n,m,q;
struct eve{
int x;
int y;
int id;
}e[MAXN];
std::mt19937 Rand(9879873);
struct D{
int ch[2],key;
long long l,r,size,len; //Treap内存的是区间
}t[MAXN*20];
int root[MAXN+1],cnt;
void pushup(int x) {t[x].size=t[x].len+t[t[x].ch[0]].size+t[t[x].ch[1]].size;}
int merge(int u,int v){
if (!u || !v) return u|v;
if (t[u].key<=t[v].key){
t[u].ch[1]=merge(t[u].ch[1],v);
pushup(u); return u;
} else {
t[v].ch[0]=merge(u,t[v].ch[0]);
pushup(v); return v;
}
}
void split_rk(int now,int rk,int &u,int &v){
if (!now) return (void)(u=v=0);
if (t[now].size-t[t[now].ch[1]].size<=rk){
u=now; split_rk(t[now].ch[1],rk-(t[now].size-t[t[now].ch[1]].size),t[now].ch[1],v);
} else {
v=now; split_rk(t[now].ch[0],rk,u,t[now].ch[0]);
}
pushup(now);
return;
}
void split_rk2(int now,int rk,int &u,int &v){ //倒着弄,使得包含第rk个数的区间在右子树内
if (!now) return (void)(u=v=0);
if (t[now].size-t[t[now].ch[0]].size<=rk){
u=now; split_rk2(t[now].ch[0],rk-(t[now].size-t[t[now].ch[0]].size),t[now].ch[0],v);
} else {
v=now; split_rk2(t[now].ch[1],rk,u,t[now].ch[1]);
}
pushup(now);
return;
}
void insert(int rtw,long long x){
t[++cnt].l=x; t[cnt].r=x; t[cnt].len=t[cnt].size=1;
t[cnt].key=Rand();
root[rtw]=merge(root[rtw],cnt);
return;
}
long long del(int rtw,int rk){
int a,b,c,d; long long res;
split_rk(root[rtw],rk-1,a,b);
split_rk2(b,t[b].size-1,d,c); //找出包含第rk个数的区间c
if (t[c].size==1) {res=t[c].l; t[c].len=t[c].size=0; root[rtw]=merge(a,d);}
else {
if (t[a].size+1==rk) {res=t[c].l; t[c].l++; t[c].len--; t[c].size--; root[rtw]=merge(merge(a,c),d);}
else if (t[a].size+t[c].size==rk) {res=t[c].r; t[c].r--; t[c].len--; t[c].size--; root[rtw]=merge(merge(a,c),d);}
else {
res=t[c].l+(rk-t[a].size)-1;
t[++cnt].l=t[c].l+(rk-t[a].size); t[cnt].r=t[c].r; t[cnt].size=t[cnt].len=t[cnt].r-t[cnt].l+1;
t[c].r=t[c].l+(rk-t[a].size)-2; t[c].size=t[c].len=t[c].r-t[c].l+1;
root[rtw]=merge(merge(merge(a,c),cnt),d);
}
}
return res;
}
void init(){
for (int i=1;i<=n+1;i++) root[i]=++cnt;
for (int i=1;i<=n;i++){
t[i].l=(i-1)*m+1; t[i].r=i*m-1;
t[i].size=t[i].len=t[i].r-t[i].l+1;
t[i].key=Rand();
}
for (int i=1;i<=n;i++) insert(n+1,1ll*i*m);
return;
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for (int i=1;i<=q;i++) {cin>>e[i].x>>e[i].y;}
init();
for (int i=1;i<=q;i++){
if (e[i].y!=m){
long long val=del(e[i].x,e[i].y);
insert(n+1,val);
long long tmp=del(n+1,e[i].x);
insert(e[i].x,tmp);
cout<<val<<endl;
} else {
long long val=del(n+1,e[i].x);
insert(n+1,val);
cout<<val<<endl;
}
}
return 0;
}
over.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!