指针版Splay

 


指针版Splay

定义

Splay 树, 或 伸展树,是一种平衡二叉查找树,它通过 Splay/伸展操作 不断将某个节点旋转到根节点,使得整棵树仍然满足二叉查找树的性质,能够在均摊 O(logN) 时间内完成插入,查找和删除操作,并且保持平衡而不至于退化为链。

一些变量

f:son[0/1]:sizecntnumis

struct edge{
	edge *f,*son[2];
	int size,cnt,num;
	bool is;
	edge(edge *A,edge *B,edge *C,int D,int E,int F){
		f=A,son[0]=B,son[1]=C,size=D,cnt=E,num=F,is=true;
	}
};

一些基础的操作

recalc

重新计算该节点的size(首先得保证这不是节点emp)

void recalc(edge *now){
	if(now->is)
	now->size=now->son[0]->size+now->son[1]->size+now->cnt;
}

get

返回now是它父亲的哪个儿子

bool get(edge *now){
	return (now==now->f->son[1]);
}

connect

将节点y的其中一边儿子whi设置为x

void connect(edge *x,edge *y,int whi){
	x->f=y;
	y->son[whi]=x;
	recalc(x);
	recalc(y);
	return ;
}

Splay操作

操作Splay即为把一个点旋转到根节点,因此我们需要通过旋转将其旋上去

Splay中最重要便是旋转,不同于一般的平衡树使用单旋,Splay采用双旋

具体而言,就是当遇到一字形的时候,我们先旋转它的父亲,再旋转它自己

否则的话我们连续旋转两次它自己

最后新的根就是它自己

而有的时候,我们可能会想把一个节点旋转到根节点的下面,此时我们就要特殊判断

代码

void splay(edge *now,edge *dist = emp){
	for(edge *fa;fa=now->f,fa->is&&fa!=dist;rotate(now))
		if(fa->f->is&&fa->f!=dist)rotate(get(now)==get(fa)?fa:now);
	if(!dist->is)rt=now;
}

这个函数意为我们将节点now旋转到一个位置,如果父亲为dist我们就停止旋转

我们可以选择传一个参数或者两个参数,如果只传一个参数的话dist自动为emp,就是将now旋转到根节点

如果传两个参数的话一般而言dist就是根节点

在循环中,我们先找出now的父亲是谁,如果它没有父亲(根)或者它的父亲为目标节点,说明我们已经完成任务,就可以退出循环了

接下来我们判断一下它父亲的父亲是不是目标节点或者空,如果是的话说明我们只需要再旋转一次就可以完成任务了

否则的话我们还要旋转两次以上,我们就判断一下是之字形还是一字形,分别旋转一次它自身或它的父亲

然后我们发现,无论是之字形还是一字形,第二次旋转我们都要旋转一次自身

因此我们可以利用for语句的特性,第一个分号之前是最开始执行的,第二个分号之前是每次执行循环语句前执行的,用来判断是否符合循环条件,第二个分号之后是每一次执行完循环语句后执行的

因此我们在第二个分号之前给fa赋值,然后fa->is&&fa!=dist是判断循环是否结束

最后不要忘记对于旋转到根节点的Splay要将根设置为now

旋转操作

我们模拟一次普通的左旋,看一下是怎么旋转的

我们的目标是

那么我们首先将now->son[1]接到fa->son[0]
然后我们把now接到gr_faget(fa)上
最后我们将fa接到now->son[1]

那么对于右旋,则是

那么我们首先将now->son[0]接到fa->son[1]
然后我们把now接到gr_faget(fa)上
最后我们将fa接到now->son[0]

我们发现左旋和右旋的区别只在于是son[0]还是son[1]

因此我们提前判断nowfa的关系,如果是右儿子就全部异或上1

那么代码就呼之欲出了

注意:因为我们最后connect的是nowfa,我们在connect的时候已经更新过节点了,因此我们在最后不用更新节点

代码

void rotate(edge *now){
	edge *fa=now->f,*gr_fa=fa->f;
	int whi=get(now);//0左1右 
	connect(now->son[1^whi],fa,0^whi);
	connect(now,gr_fa,get(fa));
	connect(fa,now,1^whi);
}

find操作

我们先通过二叉查找树的性质找到x的位置,然后把这个节点splay到根

注意到有时候我们不一定会找到该节点,此时我们就会返回该数的左边的节点或者右边的节点

代码

void find(int x){
	if(!rt->is)return ;
	edge *now=rt;
	while(x!=now->num&&now->son[x>now->num]->is)
		now=now->son[x>now->num];
	splay(now);
}

before和after

首先我们先把节点x旋转到根节点,如果该节点存在,我们就直接用循环找到前驱/后继

否则我们要判断一下这个数是不是已经是x的前驱/后继

代码

edge* before(int x){
	find(x);
	if(rt->num<x)return rt;
	edge *now=rt->son[0];
	while(now->son[1]->is)now=now->son[1];
	splay(now);
	return now;
}
edge* after(int x){
	find(x);
	if(rt->num>x)return rt;
	edge *now=rt->son[1];
	while(now->son[0]->is)now=now->son[0];
	splay(now);
	return now;
}	

Insert操作

Insert比较常规,我们只需要注意我们插入完节点要Splay到根即可

void Insert(int x){
	edge *now=rt,*fa=emp;
	while(now->is){
		if(now->num==x){now->cnt++;splay(now);return ;}
		fa=now;now=now->son[x>now->num];
	}
	now=new edge(fa,emp,emp,1,1,x);
	connect(now,fa,x > fa->num);
	splay(now);
	return ;
}

Delete操作

删除操作比较特别,它有两种实现方式

第一种是我们将要删除的点的前驱旋转到根节点,将后继旋转到根节点的右子树,那么要删除的点就在后继的左子树上

第二种是我们把要删除的点旋转到根节点,如果删除后这个点一个数不剩,就将该节点的前驱找到并旋转到根,直接连接到原来要删除的点的右子树上

第一种的话就要在写有两个参数的Splay操作,第二种就不需要,但还是比较推荐第一种写法

对于第一种写法,该节点删除完就为NULL了,因此我们要将后继的左儿子设置为emp

注意:在这里,我们最后要更新节点

代码

void Delete(int x){//第一种
	edge *tmp1=before(x);
	edge *tmp2=after(x);
	splay(tmp1);
	splay(tmp2,tmp1);
	edge *now=tmp2->son[0];
	if(now->num!=x)return ;
	if(now->cnt>1){
		now->cnt--;
		splay(now);
		return ;
	}
	connect(emp,tmp2,0);
	delete now;
	recalc(tmp2);
	recalc(tmp1);
	return ;
}
void Delete(int x){//第二种
	find(x);
	if(rt->num!=x)return ;
	if(rt->cnt>1){
		rt->cnt--;
		recalc(rt);
		return ;
	}
	edge *tmp=rt;
	rt=rt->son[0];
	before(tmp->num);
	connect(tmp->son[1],rt,1);
	delete tmp;
	recalc(rt->son[0]),recalc(rt);
	return ;
}

一些比较重要的细节

众所周知,有一种十分烦人的指针,叫做空指针(NULL)。在树形数据结构中,我们常常会不可避免地访问到空指针(例如一个节点只有右儿子,我们却访问了它的左儿子)。通常的方式是凡是访问了指针,都判断一下。然而这样效率十分低下,大量分支的存在使得代码冗长,常数变大,不易调试,可读性降低等问题。

我们可以考虑新开一个节点*emp来表示空指针。这样,即使我们遇到了一个为空的指针now,并且试图访问now->siz(应当为0),也不用担心会出现RE or WA。

因此,对于指针emp,我们将它的父亲,两个儿子都设为emp,对于size,cnt等数据我们就令之为0,然而,为了将其与一般的节点区分,我们使用is,令is为false即可,这样我们就怎样都不会访问到NULL指针了

写代码时一定要注意节点的初始化

emp节点指向的size,cnt不应当在任何时候被修改

有时候我们要查前驱和后继,如果对emp指针进行Splay感觉有不好,因此我们考虑引入两个哨兵

最开始的时候,rt指向的就是其中一个哨兵

至于它的值,分别取最大值和最小值,size,cnt都赋值为0,从而不影响答案的计算,

并且两个哨兵也算是一个节点,只是不影响答案,因此is为true

最后的最后,无论进行什么操作,到至少要Splay一次从而保证复杂度

P3369 【模板】普通平衡树

讲解都在上面了

代码

#include<bits/stdc++.h>
using namespace std;
struct edge{
	edge *f,*son[2];
	int size,cnt,num;
	bool is;
	edge(edge *A,edge *B,edge *C,int D,int E,int F){
		f=A,son[0]=B,son[1]=C,size=D,cnt=E,num=F,is=true;
	}
	void print(){
		cout<<num<<" "<<(son[0]?son[0]->num:0)<<" "<<(son[1]?son[1]->num:0)<<" "<<(f?f->num:0)<<" "<<size<<" "<<cnt<<endl;
	}
};
edge *A,*B,*C,*D,*E,*emp;
void print(edge *now){
	now->print();
	if(now->son[0]->is)print(now->son[0]);
	if(now->son[1]->is)print(now->son[1]);
	return ;
}
edge *rt;
struct Tree{
	void recalc(edge *now){
		if(now->is)
		now->size=now->son[0]->size+now->son[1]->size+now->cnt;
	}
	bool get(edge *now){
		return (now==now->f->son[1]);
	}
	void connect(edge *x,edge *y,int whi){
		x->f=y;
		y->son[whi]=x;
		recalc(x);
		recalc(y);
		return ;
	}
	void rotate(edge *now){
		edge *fa=now->f,*gr_fa=fa->f;
		int whi=get(now);//0左1右 
		connect(now->son[1^whi],fa,0^whi);
		connect(now,gr_fa,get(fa));
		connect(fa,now,1^whi);
	}
	void splay(edge *now,edge *dist = emp){
		for(edge *fa;fa=now->f,fa->is&&fa!=dist;rotate(now))
			if(fa->f->is&&fa->f!=dist)rotate(get(now)==get(fa)?fa:now);
		if(!dist->is)rt=now;
	}
	void find(int x){
		if(!rt->is)return ;
		edge *now=rt;
		while(x!=now->num&&now->son[x>now->num]->is)
			now=now->son[x>now->num];
		splay(now);
	}
	void Insert(int x){
		edge *now=rt,*fa=emp;
		while(now->is){
			if(now->num==x){now->cnt++;splay(now);return ;}
			fa=now;now=now->son[x>now->num];
		}
		now=new edge(fa,emp,emp,1,1,x);
		connect(now,fa,x > fa->num);
		splay(now);
		return ;
	}
	void Delete(int x){
		edge *tmp1=before(x);
		edge *tmp2=after(x);
		splay(tmp1);
		splay(tmp2,tmp1);
		edge *now=tmp2->son[0];
		if(now->num!=x)return ;
		if(now->cnt>1){
			now->cnt--;
			splay(now);
			return ;
		}
		connect(emp,tmp2,0);
		delete now;
		recalc(tmp2);
		recalc(tmp1);
		return ;
	}
	int check_num_rank(int x){
		find(x);
		if(rt->num>=x)return rt->son[0]->size+1;
		else return rt->son[0]->size+rt->cnt+1;
	}
	edge* before(int x){
		find(x);
		if(rt->num<x)return rt;
		edge *now=rt->son[0];
		while(now->son[1]->is)now=now->son[1];
		splay(now);
		return now;
	}
	edge* after(int x){
		find(x);
		if(rt->num>x)return rt;
		edge *now=rt->son[1];
		while(now->son[0]->is)now=now->son[0];
		splay(now);
		return now;
	}	
	edge* check_rank_num(int x){
		edge *now=rt;
		while(1){
			if(now->son[0]->is&&x<=now->son[0]->size)now=now->son[0];
			else if(now->son[1]->is&&x>now->son[0]->size+now->cnt)x-=now->son[0]->size+now->cnt,now=now->son[1];
			else {
				splay(now);
				return now;
			}
		}
	}
}T;
void init(){
	emp=new edge(NULL,NULL,NULL,0,0,0);
	emp->f=emp,emp->son[0]=emp,emp->son[1]=emp;
	emp->is=false;
	rt=new edge(emp,emp,emp,0,0,-1e8);
	edge *tmp=new edge(emp,emp,emp,0,0,1e8);
	T.connect(tmp,rt,1);
}
int main(){
	int t,op,x;
	init();
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&op,&x);
		switch(op){
			case 1:T.Insert(x);break;
			case 2:T.Delete(x);break;
			case 3:printf("%d\n",T.check_num_rank(x));break;
			case 4:printf("%d\n",T.check_rank_num(x)->num);break;
			case 5:printf("%d\n",T.before(x)->num);break;
			case 6:printf("%d\n",T.after(x)->num);break;
		}
	}	
	return 0;
}

P6136 【模板】普通平衡树(数据加强版)

代码

#include<bits/stdc++.h>
using namespace std;
struct edge{
	edge *f,*son[2];
	int size,cnt,num;
	bool is;
	int son_size(int whi){return son[whi]?son[whi]->size:0;	}
	edge(edge *A,edge *B,edge *C,int D,int E,int F){
		f=A,son[0]=B,son[1]=C,size=D,cnt=E,num=F,is=true;
	}
	void print(){
		cout<<num<<" "<<(son[0]?son[0]->num:0)<<" "<<(son[1]?son[1]->num:0)<<" "<<(f?f->num:0)<<" "<<size<<" "<<cnt<<endl;
	}
};
edge *A,*B,*C,*D,*E,*emp;
void print(edge *now){
	now->print();
	if(now->son[0]->is)print(now->son[0]);
	if(now->son[1]->is)print(now->son[1]);
	return ;
}
edge *rt;
struct Tree{
	void recalc(edge *now){
		if(now->is)
		now->size=now->son[0]->size+now->son[1]->size+now->cnt;
	}
	bool get(edge *now){
		return (now==now->f->son[1]);
	}
	void connect(edge *x,edge *y,int whi){
		x->f=y;
		y->son[whi]=x;
		recalc(x);
		recalc(y);
		return ;
	}
	void rotate(edge *now){
		edge *fa=now->f,*gr_fa=fa->f;
		int whi=get(now);//0左1右 
		connect(now->son[1^whi],fa,0^whi);
		connect(now,gr_fa,get(fa));
		connect(fa,now,1^whi);
	}
	void splay(edge *now,edge *dist = emp){
		for(edge *fa;fa=now->f,fa->is&&fa!=dist;rotate(now))
			if(fa->f->is&&fa->f!=dist)rotate(get(now)==get(fa)?fa:now);
		if(!dist->is)rt=now;
	}
	void find(int x){
		if(!rt->is)return ;
		edge *now=rt;
		while(x!=now->num&&now->son[x>now->num]->is)
			now=now->son[x>now->num];
		splay(now);
	}
	void Insert(int x){
		edge *now=rt,*fa=emp;
		while(now->is){
			if(now->num==x){now->cnt++;splay(now);return ;}
			fa=now;now=now->son[x>now->num];
		}
		now=new edge(fa,emp,emp,1,1,x);
		connect(now,fa,x > fa->num);
		splay(now);
		return ;
	}
	void Delete(int x){
		edge *tmp1=before(x);
		edge *tmp2=after(x);
		splay(tmp1);
		splay(tmp2,tmp1);
		edge *now=tmp2->son[0];
		if(now->num!=x)return ;
		if(now->cnt>1){
			now->cnt--;
			splay(now);
			return ;
		}
		connect(emp,tmp2,0);
		delete now;
		recalc(tmp2);
		recalc(tmp1);
		return ;
	}
	int check_num_rank(int x){
		find(x);
		if(rt->num>=x)return rt->son[0]->size+1;
		else return rt->son[0]->size+rt->cnt+1;
	}
	edge* before(int x){
		find(x);
		if(rt->num<x)return rt;
		edge *now=rt->son[0];
		while(now->son[1]->is)now=now->son[1];
		splay(now);
		return now;
	}
	edge* after(int x){
		find(x);
		if(rt->num>x)return rt;
		edge *now=rt->son[1];
		while(now->son[0]->is)now=now->son[0];
		splay(now);
		return now;
	}	
	edge* check_rank_num(int x){
		edge *now=rt;
		while(1){
			if(now->son[0]->is&&x<=now->son[0]->size)now=now->son[0];
			else if(now->son[1]->is&&x>now->son[0]->size+now->cnt)x-=now->son[0]->size+now->cnt,now=now->son[1];
			else {
				splay(now);
				return now;
			}
		}
	}
}T;
void init(){
	emp=new edge(NULL,NULL,NULL,0,0,0);
	emp->f=emp,emp->son[0]=emp,emp->son[1]=emp;
	emp->is=false;
	rt=new edge(emp,emp,emp,0,0,-1);
	edge *tmp=new edge(emp,emp,emp,0,0,(1<<30));
	T.connect(tmp,rt,1);
}
int main(){
	int n,m,t,op,x,last=0,ans=0;
	init();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		T.Insert(x);
	}
	while(m--){
		scanf("%d%d",&op,&x);
		switch(op){
			case 1:T.Insert(x^last);break;
			case 2:T.Delete(x^last);break;
			case 3:last=T.check_num_rank(x^last),ans^=last;break;
			case 4:last=T.check_rank_num(x^last)->num,ans^=last;break;
			case 5:last=T.before(x^last)->num,ans^=last;break;
			case 6:last=T.after(x^last)->num,ans^=last;break;
		}
	}
	cout<<ans;
	return 0;
}

P3391 【模板】文艺平衡树

Splay存在的价值在于维护区间,它无论怎么变换它的中序遍历不会改变

对于这题,对于一个需要翻转的区间[l,r],我们考虑类似第一种删除操作,将l1旋转到根,将r+1旋转到根节点的右儿子,然后翻转r+1的左子树(区间[l,r]

对于一个区间,我们不需要真的翻转,我们引入lazy_tag,给当前节点打上一个标记

不过要注意的是如果我们遇到一个lazy_tag,我们就要下放标记

仔细看代码,Splay操作会改变树的结构,但是我们没有改变Splay的写法,那会不会我们还没有下放标记我们就Splay上去了?

实际上不会,在每一次的打标记之前,我们会先找到l-1和r+1,在找l-1的时候,沿路上的标记都会被下放,因此当l-1节点Splay上来的时候沿路标记均已下放,没有影响,r+1也是同理

到了最后,我们只是修改了一下代表区间[l,r]的子树的根节点的lazy_tag便结束,因此没有影响

代码

#include<bits/stdc++.h>
using namespace std;
int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||'9'<c)f=(c=='-'?-1:1),c=getchar();
    while('0'<=c&&c<='9')x=x*10+c-'0',c=getchar();
    return x*f;
}
struct edge{
	edge *f,*son[2];
	int size,cnt,num,tag;
	bool is;
	edge(edge *A,edge *B,edge *C,int D,int E,int F){
		f=A,son[0]=B,son[1]=C,size=D,cnt=E,num=F;
		is=true;
		tag=0;
	}
	void print(){
		cout<<num<<" "<<tag<<" "<<son[0]->num<<" "<<son[1]->num<<" "<<f->num<<" "<<size<<" "<<cnt<<endl;
	}
};
int n,m,l,r;
edge *emp,*rt;
struct Tree{
	void recalc(edge *now){
		if(now->is)
			now->size=now->son[0]->size+now->son[1]->size+now->cnt;
	}
	int get(edge *now){
		return now==now->f->son[1];
	}
	void pushdown(edge *now){
		if(!now->is||!now->tag)return ;
		now->son[0]->tag^=1;
		now->son[1]->tag^=1;
		swap(now->son[0],now->son[1]);
		now->tag=0;
	}
	void connect(edge *x,edge *y,int whi){
		x->f=y;
		y->son[whi]=x;
		recalc(x);
		recalc(y);
	}
	void rotate(edge *now){
		edge *fa=now->f,*gr_fa=fa->f;
		int whi=get(now);
		connect(now->son[1^whi],fa,0^whi);
		connect(now,gr_fa,get(fa));
		connect(fa,now,1^whi);
		recalc(fa);
		recalc(now);
	}
	void splay(edge *now,edge *dist=emp){
		for(edge *fa;fa=now->f,fa->is&&fa!=dist;rotate(now))
			if(fa->f->is&&fa->f!=dist)rotate(get(now)==get(fa)?fa:now);
		if(!dist->is)rt=now;
	}
	edge* Kth(int x){
		edge *now=rt;
		while(1){
			pushdown(now);
			if(now->son[0]->is&&now->son[0]->size>=x)now=now->son[0];
			else if(now->son[1]->is&&now->son[0]->size+now->cnt<x)x-=now->son[0]->size+now->cnt,now=now->son[1];
			else {
				splay(now);
				return now;
			}
		}	
	}
	void Insert(int x){
		edge *now=rt,*fa=emp;
		while(now->is){
			fa=now;
			now=now->son[x>now->num];
		}
		now=new edge(fa,emp,emp,1,1,x);
		connect(now,fa,x>fa->num);
		splay(now);
	}
	void rev(int l,int r){
		edge *now=rt;
		edge *tmp1=Kth(l-1);
		edge *tmp2=Kth(r+1);
		splay(tmp1);
		splay(tmp2,tmp1);
		tmp2->son[0]->tag^=1;
	}
	void print(edge *now){
		if(!now->is)return ; 
		pushdown(now);
		print(now->son[0]);
		if(1<=now->num&&now->num<=n)
			printf("%d ",now->num);
		print(now->son[1]);
	}
}T;
void init(){
	emp=new edge(NULL,NULL,NULL,0,0,0);
	emp->f=emp,emp->son[0]=emp,emp->son[1]=emp;
	emp->is=false;
	rt=new edge(emp,emp,emp,0,0,0);
	edge *tmp1=new edge(emp,emp,emp,0,0,n+1);
	T.connect(tmp1,rt,1);
}
int main(){
	n=read(),m=read();
	init();
	for(int i=1;i<=n;i++)T.Insert(i);
	while(m--){
		l=read(),r=read();
		T.rev(l,r);
	}
	T.print(rt);
	return 0;
}

P4008 [NOI2003] 文本编辑器

这道题本身并不难,因为我们在操作的时候会多次改变树的结构,根节点也会改变

因此我们在操作之前要记住光标的位置,在操作结束后我们又Splay回光标的位置

那会不会我们操作后光标的位置的点就不见了呢

实际上不会,我们的根节点是光标无法操作的,它的右子树才是可能被操作的

如果我们给每一个字母都开一个节点,那未免太奢侈了,考虑其他办法

看题目,Insert操作不超过四千,我们可以尝试将录入的字符串放到一个数组里,我们只插入一个节点,在节点里面我们记录l和r,就是这个节点代表的字符串在数组中开始和结束的位置

那如果我们要找一个字符在一个节点的内部呢,记这个字符位置为x

我们可以将这一个节点分裂,因为我们的根节点是光标无法操作的,我们要找的点我们肯定需要操作(否则也不会找它)

因此我们分为[1,x)[x,cnt]

这样我们就可以大大减少节点的数量

对于Delete操作和Get操作,实际上都要遍历,只是要不要删除的区别

因此我们写在同一个函数里,Delete操作的话我们在遍历完后进行删除就可以了

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2*1024*1024+5;
char op[20],ch;
int Line[maxn],n;
struct edge{
	edge *f,*son[2];
	int size,len,num;
	bool is;
	int l,r;
	edge(edge *A,edge *B,edge *C,int D,int E,int F,int G){
		f=A,son[0]=B,son[1]=C;
		size=D,len=E,is=true;
		l=F,r=G;
		num=0;
	}
	void print(){
		cout<<l<<" "<<r<<" "<<num<<":";
		for(int i=l;i<=r;i++)cout<<(char)Line[i];
		cout<<endl;
	}
};
edge *emp,*rt,*Link=rt;
struct My_Splay{
	void recalc(edge *now){
		if(now->is)
			now->size=now->son[0]->size+now->son[1]->size+now->len;
	}
	int get(edge *now){
		return (now==now->f->son[1]);
	}
	void connect(edge *x,edge *y,int whi){
		x->f=y;
		y->son[whi]=x;
		recalc(x);
		recalc(y);
	}
	void rotate(edge *now){
		edge *fa=now->f,*gr_fa=fa->f;
		int whi=get(now);
		connect(now->son[1^whi],fa,0^whi);
		connect(now,gr_fa,get(fa));
		connect(fa,now,1^whi);
	}
	void Splay(edge *now,edge *dist = emp){
		for(edge *fa;fa=now->f,fa->is&&fa!=dist;rotate(now))
			if(fa->f->is&&fa->f!=dist)rotate(get(now)==get(fa)?fa:now);
		if(!dist->is)rt=now;
	}
	void Insert(int L,int R){
		edge *now=rt;
		edge *tmp1=Next();
		Splay(now);
		Splay(tmp1,now);
		edge *real_now=new edge(emp,emp,emp,R-L+1,R-L+1,L,R);
		connect(real_now,tmp1,0);
		recalc(tmp1),recalc(now);
	}
	void spilt(edge *now,int x){
		edge *tmp1=new edge(emp,emp,emp,x,x,now->l,now->l+x-1);
		edge *tmp2=new edge(emp,emp,emp,now->len-x,now->len-x,now->l+x,now->r);
		connect(tmp2,tmp1,1);
		connect(now->son[0],tmp1,0);
		connect(now->son[1],tmp2,1);
		connect(tmp1,now->f,get(now));
		recalc(tmp2),recalc(tmp1);
		rt=tmp1;
		delete now;
	}
	edge* find(int x){
		edge *now=rt;
		while(now->is){
			if(now->son[0]->is&&x<=now->son[0]->size)now=now->son[0];
			else if(now->son[1]->is&&x>now->son[0]->size+now->len)x-=now->son[0]->size+now->len,now=now->son[1];
			else break;
		}
		if(x>now->son[0]->size)
			x-=now->son[0]->size;
		Splay(now);
		if(x!=0)
			spilt(now,x);
		return rt;
	}
	void Delete(int x,bool whi){
		find(rt->son[0]->size+rt->len+x);
		edge *tmp1=Next();	
		Splay(Link);
		Splay(tmp1,Link);
		real_print(tmp1->son[0],whi);
		if(whi)connect(emp,tmp1,0),recalc(tmp1),recalc(Link);
	}
	void print(edge *now,bool whi){
		//test
		if(!now->is)return ;
		if(!whi){
			cout<<now->l<<" "<<now->r<<" "<<now->num<<":";
		for(int i=now->l;i<=now->r;i++)cout<<(char)Line[i];
		cout<<endl;
		}print(now->son[0],whi);
		print(now->son[1],whi);
		if(whi)delete now;
		return ;
		
	}
	void real_print(edge *now,bool whi){
		//real
		if(!now->is)return ;
		real_print(now->son[0],whi);
		if(!whi)
			for(int i=now->l;i<=now->r;i++)printf("%c",Line[i]);
		real_print(now->son[1],whi);
		if(whi)delete now;
		return ;
	}	
	edge* Last(){
		edge *now=rt;
		now=now->son[0];
		while(now->son[1]->is)now=now->son[1];
		Splay(now);
		return now;
	}
	edge* Next(){
		edge *now=rt;
		now=now->son[1];
		while(now->son[0]->is)now=now->son[0];
		Splay(now);
		return now;
	}
}T;
void init(){
	emp=new edge(NULL,NULL,NULL,0,0,0,0);
	emp->f=emp,emp->son[0]=emp,emp->son[1]=emp;
	emp->is=false;
	rt=new edge(emp,emp,emp,0,0,0,0);
	rt->num=-1;
	edge *tmp1=new edge(emp,emp,emp,0,0,0,0);
	tmp1->num=-2;
	T.connect(tmp1,rt,1);
}
int main(){
	Line[0]='~';
	init();
	int t;
	int cnt=0;
	scanf("%d",&t);
	while(t--){
		scanf("%s",op);
		if(op[0]=='M'){
			scanf("%d",&n);
			T.find(n);
			T.Splay(rt);
			Link=rt;
		}
		if(op[0]=='I'){
			Link=rt;
			scanf("%d",&n);
			for(int i=1;i<=n;i++){
				ch=getchar();
				if(ch<32||ch>126)i--;
				else Line[++cnt]=ch;
			}	
			T.Insert(cnt-n+1,cnt);
			T.Splay(Link);
		}
		if(op[0]=='D'){
			Link=rt;
			scanf("%d",&n);
			T.Delete(n,true);
		}
		if(op[0]=='G'){
			Link=rt;
			scanf("%d",&n);
			T.Delete(n,false);
			cout<<endl;
			T.Splay(Link);
		}
		if(op[0]=='P'){
			T.find(Link->son[0]->size+Link->len-1);
			Link=rt;
		}
		if(op[0]=='N'){
			T.find(Link->son[0]->size+Link->len+1);
			Link=rt;
		}
	}
	return 0;
}

参考资料

Splay入门解析【保证让你看不懂(滑稽)】

数据结构学习笔记(1) Splay树 (splay实现区间操作

Splay简易教程

题解 P3369 【【模板】普通平衡树】

splay详解(一)

Splay算法详解

浅谈splay的双旋

More Senior Data Structure · 特别浅地浅谈Splay

Splay 树

posted @   Ayaka_T  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示