标签:操作 要求 a+b turn line read date www its
\(fhq - treap\)是一种好用的平衡数,以分裂合并为基本操作,代码简洁优雅,能解决包括序列操作在内的大部分问题,适合初学平衡树的\(OIer\)们(比如我)学习和掌握。
分裂时一般传四个参数
\(now\) :当前节点
\(k\) :以权值分裂或者以排名分裂时对两棵树的要求
$a $ 和 \(b\) :传址调用,用于记录当前点的左/右儿子会变成哪一个
流程(以权值分裂为例,小于\(k\)分到左边(\(a\)这边),大于\(k\)分到右边(\(b\)这边)):
若当前点的权值小于等于我们要求的,说明这个点的左子树上的权值都小于\(k\)
那我们就要到这个点的右子树上去分。那么这个点的右儿子将会改变,传下去。同时这个点也变成了某个点的左/右儿子,修改\(a\)
大于的情况同理。
最后更新此节点。
合并操作传两个参数
\(a\) 和 \(b\) :要合并的两棵树的根节点
这里又用到了随机则平衡的思想,通过对每个点赋以随机权值,使整棵树的随机权值满足堆的性质,保证树较为平衡。
具体来说,若\(a\)的随机权值小于\(b\)的随机权值,就把\(b\)与\(a\)的右子树合并,作为新的\(a\)的右子树。
左子树的大小大于等于\(Rank\)--->在左边找排名为\(Rank\)的数
左子树的大小+1刚好等于\(Rank\)--->就是他了
其他情况--->在右子树上找排名为\(Rank-t[t[now].l].size-1\)的数
其他所有操作都是分裂合并的灵活利用
split(rt,x,a,b);
rt=merge(merge(a,New(x)),b);
分出来,建新点,和回去
split(rt,x,a,c),split(a,x-1,a,b);
rt=merge(merge(a,merge(t[b].l,t[b].r)),c);
分三棵树出来
\(a\) :小于\(x\)的数组成的树
\(b\) :等于\(x\)的数构成的树
\(c\) :大于\(x\)的数组成的树
把\(b\)的左右儿子合并,就是删掉了\(b\)
最后记得合并回来
以\(x || x-1\)为关键值分成两棵树,取左边的最大或右边的最小
#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int l,r,sz,v,rd;}t[N];
int n,rt,a,b,c,tot;
inline int read(){
int w=0;
bool f=0;
char ch=getchar();
while(ch>‘9‘||ch<‘0‘){
if(ch==‘-‘) f=1;
ch=getchar();
}
while(ch>=‘0‘&&ch<=‘9‘){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return f?-w:w;
}
inline void update(int p){
t[p].sz=t[t[p].l].sz+t[t[p].r].sz+1;
}
inline int New(int val){
t[++tot].sz=1;
t[tot].v=val;
t[tot].rd=rand();
return tot;
}
inline void split(int now,int k,int &a,int &b){
if(!now) a=b=0;
else{
if(t[now].v<=k){
a=now;
split(t[now].r,k,t[now].r,b);
}
else{
b=now;
split(t[now].l,k,a,t[now].l);
}
update(now);
}
return;
}
inline int merge(int a,int b){
if(!a||!b) return a+b;
if(t[a].rd<t[b].rd){
t[a].r=merge(t[a].r,b);
update(a);
return a;
}
else{
t[b].l=merge(a,t[b].l);
update(b);
return b;
}
return 0;
}
inline int kth(int now,int rank){
if(t[t[now].l].sz>=rank) return kth(t[now].l,rank);
if(t[t[now].l].sz+1==rank) return now;
return kth(t[now].r,rank-t[t[now].l].sz-1);
}
int main(){
n=read();
while(n--){
int opt=read(),x=read();
if(opt==1){
split(rt,x,a,b);
rt=merge(merge(a,New(x)),b);
}
if(opt==2){
split(rt,x,a,c),split(a,x-1,a,b);
rt=merge(merge(a,merge(t[b].l,t[b].r)),c);
}
if(opt==3){
split(rt,x-1,a,b);
printf("%d\n",t[a].sz+1);
rt=merge(a,b);
}
if(opt==4) printf("%d\n",t[kth(rt,x)].v);
if(opt==5){
split(rt,x-1,a,b);
printf("%d\n",t[kth(a,t[a].sz)].v);
rt=merge(a,b);
}
if(opt==6){
split(rt,x,a,b);
printf("%d\n",t[kth(b,1)].v);
rt=merge(a,b);
}
}
return 0;
}
也是模板
区间翻转
其是就是交换左右子树,打标记,标记下传,中序遍历输出。
怎么比线段树还好打
#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int l,r,sz,v,rd;bool f;}t[N];
int n,m,rt,x,y,z,tot;
inline int read(){
int w=0;
char ch=getchar();
while(ch>‘9‘||ch<‘0‘) ch=getchar();
while(ch>=‘0‘&&ch<=‘9‘){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
inline void spread(int p){
if(t[p].f){
swap(t[p].l,t[p].r);
t[t[p].l].f^=1,t[t[p].r].f^=1;
t[p].f=0;
}
return;
}
inline void update(int p){
t[p].sz=t[t[p].l].sz+t[t[p].r].sz+1;
}
inline int New(int val){
t[++tot].sz=1;
t[tot].v=val;
t[tot].rd=rand();
return tot;
}
inline void split(int p,int k,int &a,int &b){
if(!p) a=b=0;
else{
spread(p);
if(t[t[p].l].sz<k){
a=p;
split(t[p].r,k-t[t[p].l].sz-1,t[p].r,b);
}
else{
b=p;
split(t[p].l,k,a,t[p].l);
}
update(p);
}
return;
}
inline int merge(int a,int b){
if(!a||!b) return a+b;
if(t[a].rd<t[b].rd){
spread(b);
t[b].l=merge(a,t[b].l);
update(b);
return b;
}
else{
spread(a);
t[a].r=merge(t[a].r,b);
update(a);
return a;
}
return 0;
}
inline void print(int p){
spread(p);
if(t[p].l) print(t[p].l);
printf("%d ",t[p].v);
if(t[p].r) print(t[p].r);
return;
}
int main(){
n=read(),m=read();
rt=New(1);
for(int i=2;i<=n;i++) rt=merge(rt,New(i));
for(int i=1;i<=m;i++){
int l=read(),r=read();
split(rt,l-1,x,y),split(y,r-l+1,y,z);
t[y].f^=1;
rt=merge(merge(x,y),z);
}
print(rt);
puts("");
return 0;
}
标签:操作 要求 a+b turn line read date www its
原文地址:https://www.cnblogs.com/xxbbkk/p/14352909.html