关于fhq_treap的一点总结
算法原理
\(fhq - treap\)是一种好用的平衡数,以分裂合并为基本操作,代码简洁优雅,能解决包括序列操作在内的大部分问题,适合初学平衡树的\(OIer\)们(比如我)学习和掌握。
核心:分裂与合并
分裂时一般传四个参数
\(now\) :当前节点
\(k\) :以权值分裂或者以排名分裂时对两棵树的要求
$a $ 和 \(b\) :传址调用,用于记录当前点的左/右儿子会变成哪一个
流程(以权值分裂为例,小于\(k\)分到左边(\(a\)这边),大于\(k\)分到右边(\(b\)这边)):
若当前点的权值小于等于我们要求的,说明这个点的左子树上的权值都小于\(k\)
那我们就要到这个点的右子树上去分。那么这个点的右儿子将会改变,传下去。同时这个点也变成了某个点的左/右儿子,修改\(a\)
大于的情况同理。
最后更新此节点。
合并操作传两个参数
\(a\) 和 \(b\) :要合并的两棵树的根节点
这里又用到了随机则平衡的思想,通过对每个点赋以随机权值,使整棵树的随机权值满足堆的性质,保证树较为平衡。
具体来说,若\(a\)的随机权值小于\(b\)的随机权值,就把\(b\)与\(a\)的右子树合并,作为新的\(a\)的右子树。
求排名为Rank的数
左子树的大小大于等于\(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\)为关键值分成两棵树,取左边的最大或右边的最小
一定记得分完之后和回来
模板Code
#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;
}
另一种应用:区间操作
也是模板
区间翻转
其是就是交换左右子树,打标记,标记下传,中序遍历输出。
怎么比线段树还好打
Code
#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;
}
[HNOI2004]宠物收养场
自己的一个练习题
为了练习,我用了最麻烦的方法做这道题
要多想,在那之前我只能告诉自己要多想
Code
#include<bits/stdc++.h>
#define N (80010)
#define INF (998244353)
using namespace std;
struct xbk{int l,r,sz,v,rd;}t1[N],t2[N];
int n,rt1,rt2,tot1,tot2,ans,a,b,c;
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 int New1(int val){
t1[++tot1].sz=1;
t1[tot1].v=val;
t1[tot1].rd=rand();
return tot1;
}
inline int New2(int val){
t2[++tot2].sz=1;
t2[tot2].v=val;
t2[tot2].rd=rand();
return tot2;
}
inline void update1(int p){
t1[p].sz=t1[t1[p].l].sz+t1[t1[p].r].sz+1;
}
inline void update2(int p){
t2[p].sz=t2[t2[p].l].sz+t2[t2[p].r].sz+1;
}
inline void split1(int p,int k,int &a,int &b){
if(!p) a=b=0;
else{
if(k<t1[p].v){
b=p;
split1(t1[p].l,k,a,t1[p].l);
}
else{
a=p;
split1(t1[p].r,k,t1[p].r,b);
}
update1(p);
}
return;
}
inline void split2(int p,int k,int &a,int &b){
if(!p) a=b=0;
else{
if(k<t2[p].v){
b=p;
split2(t2[p].l,k,a,t2[p].l);
}
else{
a=p;
split2(t2[p].r,k,t2[p].r,b);
}
update2(p);
}
return;
}
inline int merge1(int a,int b){
if(!a||!b) return a+b;
if(t1[a].rd<t1[b].rd){
t1[a].r=merge1(t1[a].r,b);
update1(a);
return a;
}
else{
t1[b].l=merge1(a,t1[b].l);
update1(b);
return b;
}
return 0;
}
inline int merge2(int a,int b){
if(!a||!b) return a+b;
if(t2[a].rd<t2[b].rd){
t2[a].r=merge2(t2[a].r,b);
update2(a);
return a;
}
else{
t2[b].l=merge2(a,t2[b].l);
update2(b);
return b;
}
return 0;
}
inline int kth1(int p,int rank){
if(t1[t1[p].l].sz>=rank) return kth1(t1[p].l,rank);
if(t1[t1[p].l].sz+1==rank) return t1[p].v;
return kth1(t1[p].r,rank-t1[t1[p].l].sz-1);
}
inline int kth2(int p,int rank){
if(t2[t2[p].l].sz>=rank) return kth2(t2[p].l,rank);
if(t2[t2[p].l].sz+1==rank) return t2[p].v;
return kth2(t2[p].r,rank-t2[t2[p].l].sz-1);
}
int main(){
n=read();
for(int i=1;i<=n;i++){
int opt=read(),x=read();
// puts("Bowen");
if(opt==1){
if(!t2[rt2].sz){
split1(rt1,x,a,b);
rt1=merge1(merge1(a,New1(x)),b);
}
else{
int pre,suf,mx=kth2(rt2,t2[rt2].sz),mn=kth2(rt2,1);
if(x>mn){
split2(rt2,x-1,a,b);
pre=kth2(a,t2[a].sz);
rt2=merge2(a,b);
}
if(x<mx){
split2(rt2,x,a,b);
suf=kth2(b,1);
rt2=merge2(a,b);
}
if(x<mn) pre=-INF;
if(x>mx) suf=INF;
//cout<<pre<<" "<<suf<<endl;
int pp=x-pre,ss=suf-x;
if(pp<=ss){
ans+=pp;
x=pre;
split2(rt2,x,a,c),split2(a,x-1,a,b);
rt2=merge2(merge2(a,merge2(t2[b].l,t2[b].r)),c);
/*puts("T2");
for(int i=1;i<=tot2;i++) cout<<t2[i].v<<" ";
puts("");*/
}
else{
ans+=ss;
x=suf;
split2(rt2,x,a,c),split2(a,x-1,a,b);
rt2=merge2(merge2(a,merge2(t2[b].l,t2[b].r)),c);
/*puts("T2");
for(int i=1;i<=tot2;i++) cout<<t2[i].v<<" ";
puts("");*/
}
}
}
if(opt==0){
if(!t1[rt1].sz){
split2(rt2,x,a,b);
rt2=merge2(merge2(a,New2(x)),b);
}
else{
int pre,suf,mx=kth1(rt1,t1[rt1].sz),mn=kth1(rt1,1);
if(x>mn){
split1(rt1,x-1,a,b);
pre=kth1(a,t1[a].sz);
rt1=merge1(a,b);
}
if(x<mx){
split1(rt1,x,a,b);
suf=kth1(b,1);
rt1=merge1(a,b);
}
if(x<mn) pre=-INF;
if(x>mx) suf=INF;
//cout<<pre<<" "<<suf<<endl;
int pp=x-pre,ss=suf-x;
if(pp<=ss){
ans+=pp;
x=pre;
split1(rt1,x,a,c),split1(a,x-1,a,b);
rt1=merge1(merge1(a,merge1(t1[b].l,t1[b].r)),c);
}
else{
ans+=ss;
x=suf;
split1(rt1,x,a,c),split1(a,x-1,a,b);
rt1=merge1(merge1(a,merge1(t1[b].l,t1[b].r)),c);
}
}
}
while(ans>=1000000) ans-=1000000;
}
printf("%d\n",ans);
return 0;
}