Splay(不是Spaly,也不是slay,附两道练(mu)习(ban)题)
前两天学了Splay,个人感觉还是挺简单的,嗯……稍微讲讲吧,Splay的主要操作就是Splay(废话),但是最难写(想)的应该是rotate(旋转)吧,相信大家在翻到我的题解的时候一定已经看过许多题解了,图片我这里就不展示了(:滑稽
Splay主要是细节比较多,然后边界老是会炸(看写法的好坏咯)
其实并没有想象中的那么难,主要部分也就20行不到(flag),其他操作都是基于BST(别告诉我你不会)
看了我的代码就明白了(很容易的)……
首先说一下每个变量的含义
ch[x][0]:x的左儿子结点 ch[x][1]:x的右儿子结点 fa[x]:x的父亲结点
void rotate(int x){
int f=fa[x],gf=fa[f],k=get(x);//把父亲记为f,爷爷记为gf(不要想歪了)当前点是父亲的左/右儿子
if(gf) ch[gf][get(f)]=x; //边界判断
fa[x]=gf;
ch[f][k]=ch[x][!k]; //以下为正常操作,画个图就明白了
fa[ch[x][!k]]=f;
ch[x][!k]=f;
fa[f]=x;
update(f); //记得更新(顺序很重要),
}
上面是旋转操作,也是Splay中一个很重要的地方,rotate的功能就是将x转到它父亲结点的位置
什么,不要告诉我你不会写get,算了,我还是写出来吧
int get(int x){
return ch[fa[x]][1]==x;
}
下面是Splay
void Splay(int x){
int tot=0,now=x;
for(int f;fa[x]!=ff;rotate(x)){//把x一直转到根
f=fa[x];
if(get(f)==get(x)&&fa[f]!=ff) rotate(f); //这里写的是双旋,就是当父亲,爷爷,还有自己在同一线的时候要先转父亲。
}
update(x);//更新
root=x;//把根设为x
}
Splay的基本操作就是这样,目的就是把当前结点旋转到根(以后灰常有用)
然后,呃呃呃……
好像就没了(Splay的重要操作就这个),好像写得有点简短……
其它都是……
算了讲讲扩展吧。
Splay之所以那么有用,不仅仅是因为它是一颗二叉排序数(如果只是因为这个的话那它就没有意义了)。
为什么Splay常数那么大还是那么有用呢,它到底好在哪里?
让思维发散一下,为什么只能局限维护值呢?
没错,Splay还可以维护序列,甚至还可以当线段树来用。
想想为啥……
因为它有个灵活的Splay操作,当维护序列的时候,Splay中的key(用来比较的关键字)就是序列的编号,Splay的中序遍历就相当与是整个序列。(理解一下吧,写这段的时候有点神志不清……)
就因为它的灵活所以才能作为LCT的主要数据结构
给出一道比较裸的Splay模版题吧
这题还是比较好van的,就只用维护一个翻转标记就可以了。
代码……:
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
const int INF = 0x3f3f3f;
int col[N],z[N],fa[N],val[N],size[N],cnt[N],ch[N][2],tot,root;
inline void init(){ //清空数组,用于多组数据(然而这题并没有)
memset(fa,0,sizeof(fa));
memset(size,0,sizeof(size));
memset(val,0,sizeof(val));
memset(ch,0,sizeof(ch));
memset(cnt,0,sizeof(cnt));
tot=0;
root=0;
}
inline void update(int x){ //更新子树大小
if(x) size[x]=cnt[x];
if(ch[x][0]) size[x]+=size[ch[x][0]];
if(ch[x][1]) size[x]+=size[ch[x][1]];
}
void pushdown(int rt){//下传翻转标记,我这里写的是延时标记,短一点
if(col[rt]){
swap(ch[rt][0],ch[rt][1]);
col[ch[rt][0]]^=1;
col[ch[rt][1]]^=1;
col[rt]=0;
}
}
inline void newnode(int x,int f){//新建结点(用于插入)
tot++;
size[tot]=cnt[tot]=1;
fa[tot]=f;
val[tot]=x;
ch[f][x>val[f]]=tot; //这个val就是上文说的key,关键字
}
inline int get(int x){//讲过了
return ch[fa[x]][1]==x;
}
void rotate(int x){
int f=fa[x],gf=fa[f],k=get(x);
if(gf) ch[gf][get(f)]=x;
fa[x]=gf;
ch[f][k]=ch[x][!k];
fa[ch[x][!k]]=f;
ch[x][!k]=f;
fa[f]=x;
update(f);
update(x);
}
int findk(int k){//找第k大的,相当于原序列的第k个数,往下看就知道了
int now=root;
while(k){
pushdown(now); //一定要记得下放标记
if(size[ch[now][0]]>=k) now=ch[now][0];
else{
k-=size[ch[now][0]];
if(k==1) return now;k--;
now=ch[now][1];
}
}
return 0;
}
int sta[N];
inline void Splay(int x,int ff){ //对了,这里和我上文讲得有点不一样,这里不是把x旋转到根,而是讲x旋转到ff的儿子结点的位置,看下面就知道有什么用了。
int tot=0,now=x;
for(;fa[now]!=ff&&fa[now];now=fa[now]) sta[++tot]=now;//因为要先下放标记,所以要把x到ff结点之间的这条链上的结点用栈记下来,然后一个一个下放标记(反正也就logn个)
for(;tot;tot--) pushdown(sta[tot]);//下放标记
for(int f;fa[x]!=ff;rotate(x)){
f=fa[x];
if(get(f)==get(x)&&fa[f]!=ff) rotate(f);
}
if(!ff) root=x;//注意细节
}
void insert(int x){ //正常插入
if(!root) {tot++;root=tot;cnt[root]=size[root]=1;val[root]=x; return;}
int now=root,f=0;
while(1){
if(now==0){
newnode(x,f);
Splay(tot,0);
return;
}
if(x<val[now]){
f=now;
now=ch[now][0];
}else{
f=now;
now=ch[now][1];
}
}
}
int n,t;
void print(int rt){ //输出中序遍历(就是原序列)
if(!rt) return;
pushdown(rt);
print(ch[rt][0]);
if(val[rt]<=n&&val[rt]>=1) printf("%d ",val[rt]);
print(ch[rt][1]);
}
int main(){
scanf("%d%d",&n,&t);
insert(-INF);//先插入两个,方便操作
insert(INF);
for(int i=1;i<=n;i++) insert(i);//按照编号插入
for(;t--;){
int l,r;
scanf("%d%d",&l,&r);//这里主要的问题就是如何将序列中连续的一段取出来,就是先把l所在的树上的结点找出来,再把l-1旋到根,同理再把r+1旋转到l-1的右儿子的位置那里,它们夹着的那一段就是原序列的l~r。可以画个图理解一下
l=findk(l); //这里应该是l-1,因为先前插了个-INF所以这里也要+1,就变成l了
r=findk(r+2);//同上
Splay(l,0);//把l-1旋到根
Splay(r,l);//把r+1旋到l-1的儿子(右儿子)处
col[ch[ch[l][1]][0]]^=1;//然后夹着的这一段上打个旋转标记
}
print(root);//输出
return 0;
}
好像也并不是特别难,也就那么呃……一百多行,不过比起下面那题已经好很多了。。
再来一题
呃……这题不知该怎么说,写完就说明已经基本掌握了Splay了吧。
主要要注意的地方就是关于revolve操作可以通过三次reverse实现。
代码…………调了差不多一天(原来是标记……)我已经不知注释如何写了,自己理解吧。。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 300005;
int INF,mi[N],id[N],col[N],z[N],fa[N],val[N],size[N],cnt[N],ch[N][2],add[N],tot,root;
inline void init(){ //习惯而已
memset(fa,0,sizeof(fa));
memset(size,0,sizeof(size));
memset(val,0,sizeof(val));
memset(ch,0,sizeof(ch));
memset(mi,0x3f3f3f3f,sizeof(mi));
INF=mi[1];
memset(cnt,0,sizeof(cnt));
tot=0;
root=0;
}
inline void update(int x){ //上传标记
if(x) size[x]=cnt[x];
mi[x]=val[x];
if(ch[x][0]) size[x]+=size[ch[x][0]],mi[x]=min(mi[x],mi[ch[x][0]]);
if(ch[x][1]) size[x]+=size[ch[x][1]],mi[x]=min(mi[x],mi[ch[x][1]]);
}
void pushdown(int rt){//下放标记
if(col[rt]){
if(ch[rt][0]<INF&&ch[rt][0]>-INF&&ch[rt][1]<INF&&ch[rt][1]>-INF)swap(ch[rt][0],ch[rt][1]);
col[ch[rt][0]]^=1;
col[ch[rt][1]]^=1;
col[rt]=0;
}
if(add[rt]){//这是非延时标记
add[ch[rt][0]]+=add[rt];
add[ch[rt][1]]+=add[rt];
mi[ch[rt][0]]+=add[rt];
mi[ch[rt][1]]+=add[rt];
val[ch[rt][0]]+=add[rt];
val[ch[rt][1]]+=add[rt];
add[rt]=0;
}
}
inline void newnode1(int x,int y,int f){ //新建结点1
pushdown(f);
tot++;
size[tot]=cnt[tot]=1;
fa[tot]=f;
val[tot]=x;
id[tot]=y;
ch[f][y>id[f]]=tot;
}
inline void newnode2(int x,int y,int f){//建点2
pushdown(f);
tot++;
size[tot]=cnt[tot]=1;
fa[tot]=f;
val[tot]=x;
id[tot]=y;
if(y!=INF&&y!=-INF) ch[f][0]=tot;
else ch[f][y>id[f]]=tot;
}
inline int get(int x){//讲过了……
return ch[fa[x]][1]==x;
}
void rotate(int x){
int f=fa[x],gf=fa[f],k=get(x);
if(gf) ch[gf][get(f)]=x;
fa[x]=gf;
ch[f][k]=ch[x][!k];
fa[ch[x][!k]]=f;
ch[x][!k]=f;
fa[f]=x;
update(f);
update(x);
}
int findk(int k){
int now=root;
while(k){
pushdown(now);//记得下放标记
if(size[ch[now][0]]>=k) now=ch[now][0];
else{
k-=size[ch[now][0]];
if(k==1) return now;k--;
now=ch[now][1];
}
}
return 0;
}
int sta[N];
inline void Splay(int x,int ff){//啦啦啦
int tot=0,now=x;
for(;fa[now]!=ff;now=fa[now]) sta[++tot]=now;
for(;tot;tot--) pushdown(sta[tot]);
for(int f;fa[x]!=ff;rotate(x)){
f=fa[x];
if(get(f)==get(x)&&fa[f]!=ff) rotate(f);
}
if(!ff) root=x;
update(x);
}
int n,t;
void print(int rt){//debug 用
if(!rt) return;
print(ch[rt][0]);
printf("rt%d ls%d lr%d fa%d mi%d add%d size%d\n",rt,ch[rt][0],ch[rt][1],fa[rt],mi[rt],add[rt],size[rt]);
print(ch[rt][1]);
}
void insertt(int x,int y){ //呃……自己体会吧
if(!root) {tot++;root=tot;id[tot]=y;mi[root]=x;cnt[root]=size[root]=1;val[root]=x; return;}
int now=root,f=0;
while(1){
if(now==0){
newnode1(x,y,f);
Splay(tot,0);
return;
}
if(y<id[now]){
f=now;
now=ch[now][0];
}else{
f=now;
now=ch[now][1];
}
}
}
void insert(int k,int x){//码风诡异
if(k==0){
int a=root;pushdown(a);
while(ch[a][0]) a=ch[a][0],pushdown(a);
newnode1(x,tot+1,a);
Splay(tot,0);
return;
}
int a=findk(k);
if(!ch[a][1]){
pushdown(a);
tot++;
val[tot]=x;
size[tot]=cnt[tot]=1;
fa[tot]=a;
id[tot]=tot;
ch[a][1]=tot;
Splay(tot,0);
}else{
a=ch[a][1];pushdown(a);
while(ch[a][0]) a=ch[a][0],pushdown(a);
newnode2(x,tot+1,a);
Splay(tot,0);
}
}
int pre(int x){//前驱,删除用
int now=root;
pushdown(now);
now=ch[now][0];
pushdown(now);
while(ch[now][1]) now=ch[now][1],pushdown(now);
return now;
}
void clear(int rt){//删点用+1
mi[rt]=col[rt]=id[rt]=fa[rt]=z[rt]=size[rt]=cnt[rt]=ch[rt][0]=ch[rt][1]=add[rt]=0;
}
void del(int x){//删点(分类讨论)
x=findk(x+1);
Splay(x,0);
int now=root;
if(!ch[now][0]&&!ch[now][1]){
clear(root);
root=0;
}else
if(!ch[now][0]){
root=ch[now][1];
fa[root]=0;
clear(now);
}else if(!ch[now][1]){
root=ch[now][0];
fa[root]=0;
clear(now);
}else{
int pr=pre(x);
Splay(pr,0);
ch[root][1]=ch[now][1];
fa[ch[now][1]]=root;
clear(now);
}
}
int check(char c[14]){
if(c[0]=='A') return 1;
if(c[0]=='I') return 4;
if(c[0]=='D') return 5;
if(c[0]=='M') return 6;
if(c[3]=='E') return 2;
if(c[3]=='O') return 3;
}
void rev(int l,int r){//打翻转标记
l=findk(l);
r=findk(r+2);
Splay(l,0);
Splay(r,l);
col[ch[ch[l][1]][0]]^=1;
}
int main(){
init();
insertt(-INF,0);
val[1]=INF;
insertt(INF,N);
val[2]=INF;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int k;
scanf("%d",&k);
insertt(k,i);
}
scanf("%d",&t);
for(;t--;){
char c[11];
scanf(" %s",c);
int cc=check(c);
if(cc==2){
int l,r;
scanf("%d%d",&l,&r);
rev(l,r);
}
else
if(cc==4){
int l,r;
scanf("%d%d",&l,&r);
insert(l+1,r);
}
else
if(cc==1){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
x=findk(x);
y=findk(y+2);
Splay(x,0);
Splay(y,x);
add[ch[ch[x][1]][0]]+=z;
val[ch[ch[x][1]][0]]+=z;
mi[ch[ch[x][1]][0]]+=z;
update(y);
update(x);
}
else
if(cc==6){
int l,r;
scanf("%d%d",&l,&r);
l=findk(l);
r=findk(r+2);
Splay(l,0);
Splay(r,l);
printf("%d\n",mi[ch[ch[l][1]][0]]);
}
else
if(cc==5){
int k=0;
scanf("%d",&k);
del(k);
}
else
if(cc==3){
int l,r,T;
scanf("%d%d%d",&l,&r,&T);
if(T<=0) continue;
T%=(r-l+1);
if(T<=0) continue;
rev(l,r);
rev(l,l+T-1);
rev(l+T,r);
}
}
return 0;
}
//啊……
还是要做做(毒瘤)题才能掌握好一点。
到时把LCT也讲讲(:滑稽
待填坑……
已填坑 -> LCT