P3369 【模板】普通平衡树
传送门
splay模板
我只是发一个模板...
我太弱了讲不清
注意操作时可能会访问到最小值的前驱或最大值的后继
所以要多加入两个虚节点INF和 -INF防止越界
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; const int N=1e5+7; inline int read() { register int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } int n; //------------------------------Splay-------------------------------- int ch[N<<2][2],sz[N<<2],fa[N<<2],val[N<<2],num[N<<2],cnt,rt; inline void pushup(int x){ sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+num[x]; }//更新当前节点 inline void rotate(int x,int &k)//伸展 { int y=fa[x],z=fa[y],d=(ch[y][1]==x); if(y==k) k=x; else ch[z][(ch[z][1]==y)]=x; fa[x]=z; fa[y]=x; fa[ch[x][d^1]]=y; ch[y][d]=ch[x][d^1]; ch[x][d^1]=y; pushup(y); pushup(x); } inline void splay(int x,int &k)//splay { while(x!=k) { int y=fa[x],z=fa[y]; if(y!=k) { if( (ch[y][1]==x)^(ch[z][1]==y) ) rotate(x,k); else rotate(y,k); } rotate(x,k); } } inline void find(int x)//找到数x,并将它弄到树根 { int now=rt; while(ch[now][ x>val[now] ] && x!=val[now]) now=ch[now][ x>val[now] ]; splay(now,rt); } inline int Q_kth(int k)//询问第k大 { int now=rt; while(233) { if(ch[now][0]&&k<=sz[ch[now][0]]) { now=ch[now][0]; continue; } if(k>sz[ ch[now][0] ]+num[now] ) { k-=sz[ ch[now][0] ]+num[now]; now=ch[now][1]; continue; } return now; } } inline void Q_rank(int x)//询问x的排名 { find(x);//把它转到树根,然后输出左子树的大小就好了(左边都比它小,右边都比它大) printf("%d\n",sz[ch[rt][0]]/*注意不用加1,因为有多一个虚节点-INF在左边*/); } inline int pre(int x)//询问前驱,把节点转到根然后输出左子树中的最大值(即左子树最右边的节点) { find(x); if(val[rt]<x) return rt;//特判一下 int now=ch[rt][0];//左子树 while(ch[now][1]) now=ch[now][1];//一直往右走 return now; } inline int nex(int x)//同理 { find(x); if(val[rt]>x) return rt; int now=ch[rt][1]; while(ch[now][0]) now=ch[now][0]; return now; } inline void ins(int x)//插入节点 { int now=rt,f=0; while(now&&val[now]!=x)//先找到位置 { f=now; now=ch[now][ x>val[now] ]; } if(now) num[now]++,sz[now]++;//如果已经有值就直接更新 else//否则就增加节点 { now=++cnt; if(f) ch[f][ x>val[f] ]=now; val[now]=x; fa[now]=f; num[now]=sz[now]=1; } splay(now,rt);//最后要Splay一波来更新整颗树的状态 } inline void erase(int x)//删除节点,把前驱转到根,后继转到根的右儿子,那么后继的左子树有且只有当前节点 { int now=nex(x); splay(pre(x),rt); splay(now,ch[rt][1]); now=ch[now][0]; if(num[now]>1)//如果不止一个就减1 num[now]--,sz[now]--; else ch[fa[now]][0]=0;//否则就删除节点 pushup(fa[now]); pushup(fa[fa[now]]); } //------------------------------------------------------------ int main() { n=read(); ins(0x3f3f3f3f); ins(0xcfcfcfcf);//插入虚节点 int a,b; for(int i=1;i<=n;i++) { a=read(); b=read(); if(a==1) ins(b); if(a==2) erase(b); if(a==3) Q_rank(b); if(a==4) printf("%d\n",val[ Q_kth(b+1/*注意+1,因为有虚节点*/) ]); if(a==5) printf("%d\n",val[ pre(b) ]); if(a==6) printf("%d\n",val[ nex(b) ]); } return 0; }
$Upd\ in\ one\ year\ later $
上面那个链接真的只是优质讲解...模板就没那么优质了QAQ
经过了最近几天的艰苦奋斗我终于意识到了这个模板的局限性....
灵活性真的不行啊
上面的模板把重复的值合并到一个节点里面,使得代码要多注意很多恶心的细节
删除节点时因为可能会访问到边界外面所以要多插两个节点,使得细节更多...
而且对于二逼平衡树这一题,如果用线段树套 $Splay$,那么每个线段树的节点的splay都要多来两个节点,那么总节点就又要多 $2nlog_n$ 个
对于本来就十分卡线段树套$Splay$的这题来说更是难受,并且很容易因为细节问题出事,一开始用这个 $splay$ 写真的是要写疯掉了也写不出来,最后迫不得已只能重新学一份模板然后重构二逼平衡树的代码QAQ
所以这里放一个比较灵活方便的不合并重复节点的 $Splay$,真的好写很多
并且注意一下, $Splay$ 的复杂度理论上是均摊 $log_n$ 的
但是不代表它一直都是平衡的,所以对于每个复杂度为树高的操作最后都要 $splay$ 一波来保证复杂度
洛谷上这题只卡了求排名后不 $splay$ 的算法,其他操作很良心地没有卡
所以我也只有查排名才 $splay$ 2333
感觉平衡树也没什么好注释的了,反正大家都看得懂每个操作干什么2333
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2e5+7,INF=1e9+7; int n,ans; int rt,c[N][2],fa[N],sz[N],val[N],cnt; inline void pushup(int x) { sz[x]=sz[c[x][0]]+sz[c[x][1]]+1; } inline void rotate(int x,int &k) { int y=fa[x],z=fa[y],d=(c[y][1]==x); y==k ? k=x : c[z][c[z][1]==y]=x; fa[x]=z; fa[y]=x; fa[c[x][d^1]]=y; c[y][d]=c[x][d^1]; c[x][d^1]=y; pushup(y); pushup(x); } inline void splay(int x,int &k) { while(x!=k) { int y=fa[x],z=fa[y]; if(y!=k) { if(c[y][0]==x ^ c[z][0]==y) rotate(x,k); else rotate(y,k); } rotate(x,k); } } inline void Q_rank(int k) { int x=rt,res=0,f=0; while(x) { f=x; if(val[x]>=k) x=c[x][0]; else res+=sz[c[x][0]]+1,x=c[x][1]; } if(f) splay(f,rt); printf("%d\n",res+1); } inline int Q_kth(int x,int k) { while(233) { if(sz[c[x][0]]+1==k) return x; if(sz[c[x][0]]>=k) { x=c[x][0]; continue; } k-=sz[c[x][0]]+1; x=c[x][1]; }//理论上此处要有splay } inline void ins(int k) { int x=rt,f=0; while(x) f=x,x=c[x][k>val[x]]; x=++cnt; fa[x]=f; val[x]=k; sz[x]=1; if(f) c[f][k>val[f]]=x; if(rt) splay(x,rt); else rt=x; } inline void del(int k)//删除就把节点转到根然后把左右子树直接并起来 { int x=rt; while(val[x]!=k) x=c[x][k>val[x]]; splay(x,rt); if(c[x][0]&&c[x][1]) { int y=Q_kth(rt,sz[c[x][0]]); splay(y,c[x][0]); x=rt; c[y][1]=c[x][1]; fa[c[x][1]]=y; rt=y; fa[rt]=0; pushup(y); } else if(c[x][0]) fa[c[x][0]]=0,rt=c[x][0]; else if(c[x][1]) fa[c[x][1]]=0,rt=c[x][1]; else rt=0; } void pre(int x,int k)//理论上前驱和后继跑完都要splay最底下的节点 { if(!x) return; if(ans<val[x]&&k>val[x]) ans=val[x]; pre(c[x][k>val[x]],k); } void nex(int x,int k) { if(!x) return; if(ans>val[x]&&k<val[x]) ans=val[x]; nex(c[x][k>=val[x]],k); } int main() { n=read(); int a,b; while(n--) { a=read(),b=read(); if(a==1) { ins(b); continue; } if(a==2) { del(b); continue; } if(a==3) { Q_rank(b); continue; } if(a==4) { printf("%d\n",val[Q_kth(rt,b)]); continue; } if(a==5) { ans=-INF; pre(rt,b); printf("%d\n",ans); continue; } ans=INF; nex(rt,b); printf("%d\n",ans); } return 0; }