浅谈fhq treap
一、简介
fhq treap 与一般的treap主要有3点不同
1、不用旋转
2、以merge和split为核心操作,通过它们的组合实现平衡树的所有操作
3、可以可持久化
二、核心操作
代码中val表示节点权值,pri表示节点的优先级,维护小根堆
1、split
将1个treap分裂为两个treap
分裂主要有两种:以权值k作为分界点、以位置k作为分界点
①以权值k作为分界点
设原来的treap根节点为root,分裂后的<=k的treap A 的根节点为x,>k的treap B 的根节点为y
当前节点指针now从root开始,在treap A 的x位置加点,treap B 的y位置加点
若now的权值<=k,那么以now为根的左子树全部包含在A中,
把now给x,去now的右子树,x变成x的右子树,y还是y
若now的权值>k,那么以now为分的右子树全部包含在B中,
把now给y,去now的左子树,x还是x,y变成y的左子树
void split(int now,int k,int &x,int &y) { if(!now) x=y=0; else { if(val[now]<=k) { x=now; split(ch[now][1],k,ch[now][1],y); } else { y=now; split(ch[now][0],k,x,ch[now][0]); } update(now); } }
②以位置k作为分界点
设原来的treap根节点为root,分裂后的前k个节点的treap A 的根节点为x,剩下的节点的treap B 的根节点为y
判断放在哪颗treap的标准改成k和子树大小的比较,原理同上
void split(int now,int k,int &x,int &y) { if(!now) x=y=0; else { if(k<=siz[ch[now][0]]) { y=now; split(ch[now][0],k,x,ch[now][0]); } else { x=now; split(ch[now][1],k-siz[ch[now][0]]-1,ch[now][1],y); } update(now); } }
2、merge
合并以x为根的treap A 和 以y为根的treap B
前提:A的权值全部<B的权值
注意在merge的过程中,维护好堆和二叉搜索树的性质
若优先级x<y,
维护堆的性质-->y成为x的子节点
维护二叉搜索树的性质-->y在x的右子树
若优先级x>y,
维护堆的性质-->x成为y的子节点
维护二叉搜索树的性质-->x在y的左子树
int merge(int x,int y) { if(!x || !y) return x+y; if(pri[x]<pri[y]) { ch[x][1]=merge(ch[x][1],y); update(x); return x; } else { ch[y][0]=merge(x,ch[y][0]); update(y); return y; } }
三、单点操作
1、建树
建树有两种方式:nlogn 一个一个插、n 直接建
这里主要说后者
每次取mid作为当前子树的根节点即可
int new_node(int v) { siz[++tot]=1; val[tot]=v; pri[tot]=rand(); return tot; } int build(int l,int r) { if(l>r) return 0; int mid=l+r>>1,v=val[mid]; int now=new_node(v); ch[now][0]=build(l,mid-1); ch[now][1]=build(mid+1,r); update(now); return now; }
2、插入
添加权值为v的点
1、按权值以v为分界点,split成两棵treapA(<=v),B( >v )
2、合并A和v,得到C
3、合并C和B
merge要求x的权值<y决定了 合并的顺序
split(root,v,x,y);
root=merge(merge(x,new_node(v)),y);
3、删除
删除一个权值为v的点
1、按权值以v为分界点,split成两棵treap A(<=v),B( >v)
2、把A按权值以v-1为分界点,split成两棵treap C(<=v-1) ,D(=v)
3、合并D的根节点的左右子树,相当于删除根节点,得到treap E
4、合并C和E,得F
5、合并F和B
split(root,v,x,z); split(x,v-1,x,y); y=merge(ch[y][0],ch[y][1]); root=merge(merge(x,y),z);
4、查询v的排名
1、按权值以v-1为分界点,split成两棵treap A(<=v-1) B(>=v)
2、A的大小+1 即为权值v的排名
3、合并A、B
split(root,v-1,x,y); cout<<siz[x]+1<<'\n'; root=merge(x,y);
5、查询排名为x的数
int get_kth(int now,int k) { while(1) { if(k<=siz[ch[now][0]]) now=ch[now][0]; else { k-=siz[ch[now][0]]; if(k==1) return now; k--; now=ch[now][1]; } } } cout<<val[get_kth(root,v)]<<'\n';
6、查找比v小的最大的数(前驱)
1、按权值以v-1为分界点,split成两棵treap A(<=v-1),B(>=v)
2、在A中查找A的排名最靠后的数,输出
3、合并A和B
split(root,v-1,x,y); cout<<val[get_kth(x,siz[x])]<<'\n'; root=merge(x,y);
7、查询比v大的最小的数(后继)
1、按权值以v为分界点,split成两棵treap A(<=v),B(>v)
2、在B中找最靠前的数,输出
3、合并A和B
split(root,v,x,y); cout<<val[get_kth(y,1)]<<'\n'; root=merge(x,y);
四、区间操作
对区间[l,r]执行某种操作
1、以位置r作为分界点,split成两棵treap A(前r个节点)、B(位置r之后的节点)
2、在A中以位置l-1为分界点,split成两棵treap C(前l-1个节点)、D(第l到第r个节点)
3、对D执行操作
注意因为执行区间操作,增加2个虚拟节点,最前面一个,最后面一个,所有节点后移一位
加虚拟节点的原因是 如果对区间[1,x]或[x,n]操作,那我们需要用到1-1 号 点和n+1号店
split(root,r+1,a,b); split(a,l,c,d); work(d)
五、模板
1、普通平衡树
https://www.luogu.org/problemnew/show/P3369
#include<ctime> #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; #define N 100001 int root; int tot; int fa[N],ch[N][2],val[N],pri[N],siz[N]; void read(int &x) { x=0; int f=1; char c=getchar(); while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); } while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } x*=f; } void update(int x) { siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1; } int new_node(int v) { siz[++tot]=1; val[tot]=v; pri[tot]=rand(); return tot; } int merge(int x,int y) { if(!x || !y) return x+y; if(pri[x]<pri[y]) { ch[x][1]=merge(ch[x][1],y); update(x); return x; } else { ch[y][0]=merge(x,ch[y][0]); update(y); return y; } } void split(int now,int k,int &x,int &y) { if(!now) x=y=0; else { if(val[now]<=k) { x=now; split(ch[now][1],k,ch[now][1],y); } else { y=now; split(ch[now][0],k,x,ch[now][0]); } update(now); } } int get_kth(int now,int k) { while(1) { if(k<=siz[ch[now][0]]) now=ch[now][0]; else { k-=siz[ch[now][0]]; if(k==1) return now; k--; now=ch[now][1]; } } } int main() { srand(time(0)+20001024); int n,u,v; int x,y,z; read(n); while(n--) { read(u); read(v); if(u==1) { split(root,v,x,y); root=merge(merge(x,new_node(v)),y); } else if(u==2) { split(root,v,x,z); split(x,v-1,x,y); y=merge(ch[y][0],ch[y][1]); root=merge(merge(x,y),z); } else if(u==3) { split(root,v-1,x,y); cout<<siz[x]+1<<'\n'; root=merge(x,y); } else if(u==4) { cout<<val[get_kth(root,v)]<<'\n'; } else if(u==5) { split(root,v-1,x,y); cout<<val[get_kth(x,siz[x])]<<'\n'; root=merge(x,y); } else { split(root,v,x,y); cout<<val[get_kth(y,1)]<<'\n'; root=merge(x,y); } } }
2、文艺平衡树
https://www.luogu.org/problemnew/show/P3391
#include<ctime> #include<cstdio> #include<cstdlib> #include<iostream> using namespace std; #define N 100005 int n; int tot,root; int siz[N],ch[N][2],val[N],pri[N]; bool rev[N]; void read(int &x) { x=0; int f=1; char c=getchar(); while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); } while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } x*=f; } int new_node(int v) { siz[++tot]=1; val[tot]=v; pri[tot]=rand(); return tot; } void update(int x) { siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]]; } int build(int l,int r) { if(l>r) return 0; int mid=l+r>>1,v=mid-1; int now=new_node(v); ch[now][0]=build(l,mid-1); ch[now][1]=build(mid+1,r); update(now); return now; } void down(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); rev[ch[x][0]]^=1; rev[ch[x][1]]^=1; } int merge(int x,int y) { if(!x || !y) return x+y; if(rev[x]) down(x); if(rev[y]) down(y); if(pri[x]<pri[y]) { ch[x][1]=merge(ch[x][1],y); update(x); return x; } else { ch[y][0]=merge(x,ch[y][0]); update(y); return y; } } void split(int now,int k,int &x,int &y) { if(!now) x=y=0; else { if(rev[now]) down(now); if(k<=siz[ch[now][0]]) { y=now; split(ch[now][0],k,x,ch[now][0]); } else { x=now; split(ch[now][1],k-siz[ch[now][0]]-1,ch[now][1],y); } update(now); } } void reverse(int l,int r) { int a,b,c,d; split(root,r+1,a,b); split(a,l,c,d); rev[d]^=1; root=merge(merge(c,d),b); } void dfs(int x) { if(rev[x]) down(x); if(ch[x][0]) dfs(ch[x][0]); if(val[x]>=1 && val[x]<=n) cout<<val[x]<<' '; if(ch[x][1]) dfs(ch[x][1]); } int main() { srand(time(0)+20001024); int m; read(n); read(m); root=build(1,n+2); int l,r; while(m--) { read(l); read(r); reverse(l,r); } dfs(root); }