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模版题吧

bzoj 3223: Tyvj 1729 文艺平衡树

这题还是比较好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;
}

 好像也并不是特别难,也就那么呃……一百多行,不过比起下面那题已经好很多了。。

再来一题

poj 2580(毒瘤)

呃……这题不知该怎么说,写完就说明已经基本掌握了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

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值