在多次学习splay后,我终于理解并码出了整份代码
参考了https://tiger0132.blog.luogu.org/slay-notes的博客
具体实现原理在上面这篇博客和百度中可以查到,接下来我们看一下代码
#include<bits/stdc++.h> using namespace std; #define INF 0x3f3f3f3f #define ls st[p].ch[0] #define rs st[p].ch[1] inline int read(){ int w=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ w=(w<<3)+(w<<1)+ch-48; ch=getchar(); } return w*f; } int n,m,cnt,tot,root; struct node{ int val,cnt,sum,ch[2],f; }st[1000010];//val表示当前值,cnt表示出现次数,sum表示包括自己在内的子树大小,ch[0]为左儿子,ch[1]为右儿子 inline void push_up(int x){ st[x].sum=st[st[x].ch[0]].sum+st[st[x].ch[1]].sum+st[x].cnt; }//上推,子树大小为左子树加右子树加自身 inline void connect(int x,int fa,int son){ st[x].f=fa;st[fa].ch[son]=x; }//重新连接父子节点 inline bool identify(int x){ return st[st[x].f].ch[1]==x; }//判断自己是左儿子还是右儿子 inline void rotate(int x){ int y=st[x].f;int z=st[y].f;//父亲和曾祖父 int yson=identify(x);int zson=identify(y);//身份认定 int b=st[x].ch[yson^1];//往上连接的节点一定是st[x].ch[yson^1]可以手画几张图推一下情况 connect(b,y,yson);connect(y,x,(yson^1));connect(x,z,zson); push_up(y);push_up(x);return; }//rotate的实现原理在上面那个博客里有详细介绍 inline void splay(int x,int goal){ while(st[x].f!=goal){ int y=st[x].f;int z=st[y].f; int yson=identify(x);int zson=identify(y); if(z!=goal){ if(yson==zson) rotate(y); else rotate(x); } rotate(x); } if(!goal) root=x; return; }//splay操作就是将节点向目标不断旋转 inline void insert(int x){ int now=root;int f=0; while(now&&st[now].val!=x){//原平衡树上不一定有现在查询的这个值,所以在往下跳的同时记录上一步,也就是父节点的位置 f=now; now=st[now].ch[x>st[now].val]; } if(now){ st[now].cnt++;//如果查询到了,并且发现已经有这个节点,就cnt++; } else{ tot++;now=tot;//如果发现原先没有这么个节点,就新建一个 if(f){ st[f].ch[x>st[f].val]=now;//从父亲连向儿子 } st[now].ch[0]=st[now].ch[1]=0;//儿子连向父亲 st[now].sum=st[now].cnt=1; st[now].f=f;st[now].val=x; } splay(now,0);return;//将当前节点旋转到根 } inline void find(int x){ int now=root;//本操作与上面的操作类似,找到当前值的节点,然后把其旋转到根上 if(!now) return; while(st[now].ch[x>st[now].val]&&x!=st[now].val){ now=st[now].ch[x>st[now].val]; } splay(now,0);return; } inline int Next(int x,int f){//当f=0时查询的是前驱,f=1时查询的是后继 find(x);int now=root; if(st[now].val<x&&!f) return now; if(st[now].val>x&&f) return now; now=st[now].ch[f]; while(st[now].ch[f^1]) now=st[now].ch[f^1]; return now; } inline void Delete(int x){//删除节点 int la=Next(x,0);int ne=Next(x,1);//查询该点的前驱后继 splay(la,0);splay(ne,la);//现将前驱转到根,再将后继转向前驱,此时后继的左儿子就是当前要删的节点了 int now=st[ne].ch[0]; if(st[now].cnt>1){//如果该点的cnt>1,cnt--即可,要不就把子节点的联系断掉就好了 st[now].cnt--; splay(now,0); } else{ st[ne].ch[0]=0; } return; } inline int k_th(int x){//查询第k大 int now=root; if(st[now].sum<x) return false;//如果总数不够就大力返回FALSE; while(true){ int lson=st[now].ch[0];//左儿子 if(x>st[lson].sum+st[now].cnt){//如果x比左儿子的大小加上当前节点出现次数还大,就向右子树查询,记得减去之前的数值 x-=st[lson].sum+st[now].cnt; now=st[now].ch[1]; } else if(st[lson].sum>=x){ now=lson;//如果x比左子树的大小小,就向左查询 } else return st[now].val;//如果不属于以上两种情况就是在当前节点了 } } int main(){//主函数就是根据题目要求大力操作了 m=read();int i,j,k;root=0; insert(INF);insert(-INF); while(m--){ int opt=read(),x=read(); if(opt==1) insert(x); if(opt==2) Delete(x); if(opt==3){ find(x);printf("%d ",st[st[root].ch[0]].sum); } if(opt==4){ printf("%d ",k_th(x+1)); } if(opt==5){ printf("%d ",st[Next(x,0)].val); } if(opt==6){ printf("%d ",st[Next(x,1)].val); } } return 0; }
普通平衡树这道题大概就是这样了,接下来会跟进有关题目的训练及题解。