蒟蒻林荫小复习——Splay
首先表示对YYB大佬的崇高敬意虽然大佬根本不知道林荫是个神马东西 !
在这里学的:yyb大佬的教程!
好吧,我回来填坑了!
首先声明一下定义
struct p { int v,ff,ch[2],size,cnt; }; p t[150001];
t数组就是记录整颗树的数组,v代表当前点的权值,ff代表当前点的父亲,ch[0,1]分别代表左右子树(左子树上的元素小于根,右子树则大于),size代表以该节点为根的子树中元素个数,cnt代表当前点上有多少个元素(权值均为v)
pushup维护size
void pushup(int x) { t[x].size=t[t[x].ch[0]].size+t[t[x].ch[1]].size+t[x].cnt; }
下面是一次旋转rotate
先想一下一次旋转会造成什么影响:假设当前节点为x,父亲为y,将x旋转到y上,x是y的k子树(k=0或1,代表左右子树)
那么:
- x的k^1儿子会变成y(因为(y>x)==k^1,那么假如k=0,y>x,那么将y作为x的右子树保证大于x,若k=1,y<x,将y作为x的左子树仍保证小于x)
- 原来x的k^1儿子被过继给y做k儿子(假定该儿子为s,若k=0,s>x且s<y[否则s会存在于y的右子树],作为y的左子树保证小于y
- 没了
然后注意适当的时候维护他们的ff就可以了。
void rotate(int x) { int y=t[x].ff; int z=t[y].ff; int k=(t[y].ch[1]==x);
t[z].ch[t[z].ch[1]==y]=x; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[x].ff=z; t[y].ff=x; pushup(y); pushup(x); }
下面就是Splay的核心了,Splay,这也是它快的原因
Splay的作用是将x旋转成为goal的儿子,若goal==0则将x旋转到root的位置。
现在先想一种情况:
好的,图片有点大,图片引用自yyb大佬的博客。
考虑这种情况,现在我们将x旋转到root(z)的位置,然后我们查询b的位置,旋转前查询路径:Z—》Y—》X—》B
但是旋转后呢?(自己画个图)路径还是由X—》Z—》Y—》B,因为这个时候,B作为X的右儿子被过继给了Y,而B的养父Y由于被X旋转下来的Z压着,留在了社会的最底层(批斗X)这时,树的深度没有变化。
然而无论如何向上层社会进步的同时都会付出一定的代价,X向上旋转的时候B一定会被过继给Y,两次连续的旋转后就留在了社会底层,这就不平衡了对吧。那我们就顺便提高一波Y的社会地位,先将Y向上旋转,这个时候Y的左子树是X,右子树是Z,然后再将X旋转上去。
可能有的小可爱已经发现了一个问题,上面所说的问题出现在Y和X与自己的父亲大小关系相同的情况下,换言之就是X和Y同为自己父亲的左或者右子树。
因为如果Y和X不同为自己父亲的左或右子树的话,Y不会受到X和Z旋转的影响,也就是说Y和Z在X的不同子树上,不会出现Y被Z压着的情况,这个时候,树的深度就变浅了HHHHH。
好了,林荫开始杠精了,如果在把X翻上去之后要查询Z的儿子的话怎么办?那就再把Z的儿子翻上来不就好啦,翻着翻着树就变矮变宽了,这也就是平衡树的精髓所在。
对了,这个时候,如果目标goal==0的话,root就是X啦
void Splay(int x,int goal) { while(t[x].ff!=goal) { int y=t[x].ff; int z=t[y].ff; if(z!=goal) { (t[y].ch[0]==x)^(t[z].ch[0]==y)?rotate(x):rotate(y); } rotate(x); } if(goal==0) { root=x; } }
insert就没啥好说的了,一溜烟往下找,找到了计数器++,找不到自力更生新开个点
void insert(int x) { int u=root,ff; while(u&&t[u].v!=x) { ff=u; u=t[u].ch[t[u].v<x]; } if(t[u].v==x) { t[u].cnt++; } else { u=++tot; t[u].v=x; t[u].ff=ff; if(ff) t[ff].ch[x>t[ff].v]=u; t[u].cnt=1; t[u].size=1; t[u].ch[0]=t[u].ch[1]=0; } Splay(u,0); }
Find的操作意义是找到权值为x的节点并将其翻到树顶,也没啥可说的,一溜烟往下找,找到了就翻上去即可。
void Find(int x) { int u=root; while(t[u].ch[x>t[u].v]&&t[u].v!=x) { u=t[u].ch[x>t[u].v]; } Splay(u,0); }
下面是前驱后缀查询操作,f=0代表查询前驱,=1代表查询后缀。
int Next(int x,int f) { Find(x); int u=root; if(t[u].v>x&&f) { return u; } if(t[u].v<x&&!f) { return u; } u=t[u].ch[f]; while(t[u].ch[f^1]) { u=t[u].ch[f^1]; } return u; }
这里有一点需要注意,因为Find(x)中有Splay(u,0)语句了,实际上这个时候u点的权值就是x,这里是yyb大佬博客中的一个小坑,中间两个if是没有意义的。
下面是Kth(实际上是将序列从小到大排开第K个)这个挺简单的。
int Kth(int x) { int u=root; if(t[u].size<x) { return -INF; } while(1) { int y=t[u].ch[0]; if(t[y].size+t[u].cnt<x) { x-=t[y].size+t[u].cnt; u=t[u].ch[1]; } else { if(t[y].size>=x) { u=t[u].ch[0]; } else return t[u].v; } } }
最后是Delete
怎样删除一个可爱的节点,那肯定是将其先变成叶子节点。查找这个节点的前驱后继,将前驱旋转到根,后继旋转到成为前驱的儿子,这个时候,目标节点就一定是后继节点的左儿子并且是叶子节点。那么就搞死它啦!
void Delete(int x)//删除x { int last=Next(x,0);//查找x的前驱 int next=Next(x,1);//查找x的后继 splay(last,0);splay(next,last); //将前驱旋转到根节点,后继旋转到根节点下面 //很明显,此时后继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点 int del=t[next].ch[0];//后继的左儿子 if(t[del].cnt>1)//如果超过一个 { t[del].cnt--;//直接减少一个 splay(del,0);//旋转 } else t[next].ch[0]=0;//这个节点直接丢掉(不存在了) }//引用自yyb大佬
完结撒花!
两年后了:全装SPLAY!
#include<iostream> #include<cstdio> using namespace std; struct PE { int v,ff,ch[2],size,cnt; }; PE t[150001]; int n,a1,a2,a3,tot=0,INF=998244353,root=0; int ls(int x) { return t[x].ch[0]; } int rs(int x) { return t[x].ch[1]; } void pushup(int x) { t[x].size=t[x].cnt+t[ls(x)].size+t[rs(x)].size; } void rotate(int x) { int y=t[x].ff; int z=t[y].ff; int k=(t[y].ch[1]==x); t[z].ch[t[z].ch[1]==y]=x; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[x].ff=z; t[y].ff=x; pushup(y); pushup(x); } void Splay(int x,int goal) { while(t[x].ff!=goal) { int y=t[x].ff; int z=t[y].ff; if(z!=goal) { (t[y].ch[0]==x)^(t[z].ch[0]==y)?rotate(x):rotate(y); } rotate(x); } if(goal==0) root=x; } void Insert(int x) { int u=root,ff=0; while(u&&t[u].v!=x) { ff=u; u=t[u].ch[t[u].v<x]; } if(t[u].v==x) { t[u].cnt++; } else { u=++tot; t[u].v=x; t[u].ff=ff; if(ff) { t[ff].ch[t[ff].v<x]=u; } t[u].size=1; t[u].cnt=1; t[u].ch[0]=t[u].ch[1]=0; } Splay(u,0); } void Find(int x) { int u=root; while(t[u].ch[t[u].v<x]&&t[u].v!=x) { u=t[u].ch[t[u].v<x]; } Splay(u,0); } int Nxt(int x,int f) { Find(x); int u=root; if(t[u].v>x&&f) return u; if(t[u].v<x&&!f) return u; u=t[u].ch[f]; while(t[u].ch[f^1]) { u=t[u].ch[f^1]; } return u; } int Kth(int x) { int u=root; if(t[u].size<x) { return -INF; } while(1) { int y=t[u].ch[0]; if(t[y].size+t[u].cnt<x) { x=x-t[y].size; x=x-t[u].cnt; u=t[u].ch[1]; } else { if(t[y].size>=x) u=t[u].ch[0]; else return t[u].v; } } } int Rank(int x) { Find(x); int u=root; if(t[u].v==x) { return t[ls(u)].size+1; } if(t[u].v<x) { return t[ls(u)].size+t[u].cnt+1; } else return t[ls(u)].size+1; } void Delete(int x) { int last=Nxt(x,0); int nxt=Nxt(x,1); Splay(last,0); Splay(nxt,last); int del=t[nxt].ch[0]; if(t[del].cnt>1) { t[del].cnt--; Splay(del,0); } else { t[nxt].ch[0]=0; } } int main() { Insert(INF); Insert(-INF); scanf("%d",&n); for(int i=1;i<=n;i++) { cin>>a1>>a2; if(a1==1) { Insert(a2); } if(a1==2) { Delete(a2); } if(a1==3) { cout<<Rank(a2)-1<<endl; } if(a1==4) { a2++; cout<<Kth(a2)<<endl; } if(a1==5) { cout<<t[Nxt(a2,0)].v<<endl; } if(a1==6) { cout<<t[Nxt(a2,1)].v<<endl; } } return 0; }