史上最菜的splay讲解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/zyszlb2003/article/details/95312018

先了解一下什么是平衡树吧。

之后进行各种玄学操作,但都离不开平衡树的基本性质:

满足根存在一性质,大于左子树的同一性质,小于右子树的同一性质

请记住这句话

定义结构体(以普通平衡树为例):

struct node{int d,n,c,f,son[2];}t[N];int len,root;//son[0]为左,son[1]为右

先讲讲五个基本操作吧。

u p d a t e update 操作(也就是一个向上传递的操作)

void update(int p)
{
	int l=t[p].son[0],r=t[p].son[1];
	t[p].c=t[l].c+t[r].c+1;
}

a d d add 操作(加点操作)

void add(int d,int f)
{
    ++len;t[len].d=d;t[len].f=f;t[len].n=t[len].c=1;
    t[len].son[0]=t[len].son[1]=0;
    d<t[f].d?t[f].son[0]=len:t[f].son[1]=len;//运用性质
}

难点:(其实也不难)

r o t a t e rotate 操作:

以右旋为例,我们要让 p p 成为 g f gf 的孩子。

s t e p 1 step1
在这里插入图片描述

为了满足平衡树的性质

应变成:

在这里插入图片描述

其他情况类似。

void rotate(int p,int w)
{
	int f=t[p].f,gf=t[f].f;
	int r=t[p].son[w],R=f;t[R].son[w^1]=r;if(r)t[r].f=R;
	r=p;R=gf;t[R].son[0]==f?t[R].son[0]=r:t[R].son[1]=r;t[r].f=R;
	r=f;R=p;t[R].son[w]=r;t[r].f=R;update(f);update(p);
}

s p l a y splay 操作( s p l a y ( p , r t ) splay(p,rt) 即让 p p 成为 r t rt 的孩子,同时保持平衡树结构)

void splay(int p,int rt)
{
	while(t[p].f!=rt)
	{
		int f=t[p].f,gf=t[f].f;
		if(gf==rt)t[f].son[0]==p?rotate(p,1):rotate(p,0);
		else
		{
			if(t[f].son[0]==p&&t[gf].son[0]==f)rotate(f,1),rotate(p,1);
			else if(t[f].son[0]==p&&t[gf].son[1]==f)rotate(p,1),rotate(p,0);
			else if(t[f].son[1]==p&&t[gf].son[0]==f)rotate(p,0),rotate(p,1);
			else rotate(f,0),rotate(p,0);
		}
	}
	if(!rt)root=p;//让p成为root
}

g e t i d ( f i n d i d , f i n d n u m ) get_{id}(find_{id},find_{num}) 操作(找最接近 d d 值的点)

r o o t root 出发,根据平衡树性质,往下搜索。

int get_id(int d)
{
    int p=root;
    while(t[p].d!=d)
    {
        if(d<t[p].d)
        {
            if(!t[p].son[0])break;
            p=t[p].son[0];
        }
        if(d>t[p].d)
        {
            if(!t[p].son[1])break;
            p=t[p].son[1];
        }
    }
    return p;
}

找到 d d 值的排名(第几小), r k rk 操作。

int rk(int d)
{
    int p=get_id(d);splay(p,0);
    return t[t[p].son[0]].c+1;
}

找到第 k k 小的值, r k c rkc 操作

int rkc(int k)
{
	int p=root;
	while(1)
	{
		pushdown(p);
		int lc=t[p].son[0],rc=t[p].son[1];
		if(k<=t[lc].c)p=lc;
		else if(k>t[lc].c+1)k-=t[lc].c+1,p=rc;
		else break;
	}
	return p;
}

找到 d d 值的位置的前驱, p r v prv 操作

int prv(int d)
{
    int p=get_id(d);splay(p,0);
    if(d<=t[p].d&&t[p].son[0])
        for(p=t[p].son[0];t[p].son[1];p=t[p].son[1]);
    if(d<=t[p].d)p=0;//这句话说明找不到前驱
    return p;
}

这里需要说明一下,由于 g e t i d get_{id} 找到的是一个最接近d的值(或大或小,含等于)

(往下潜到左子树(右子树)为空时停止,因此不存在程序找不到前驱,而实际存在前驱的情况。)

有时根据特殊要求,可以排掉等号。

n x t nxt 操作类似。

int nxt(int d)
{
    int p=get_id(d);splay(p,0);
    if(d>=t[p].d&&t[p].son[1])
        for(p=t[p].son[1];t[p].son[0];p=t[p].son[0]);
    if(d>=t[p].d)p=0;
    return p;
}

这就是普通平衡树的全部操作。

AC code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#define gc getchar()
using namespace std;
const int N=1e5+10;
inline void qr(int &x)
{
	x=0;int f=1;char c=gc;
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
	x*=f;
}
void qw(int x)
{
	if(x<0)x=-x,putchar('-');
	if(x/10)qw(x/10);
	putchar(x%10+48);
}
struct node{int d,c,n,son[2],f;}t[N];int root,len;
void update(int p){t[p].c=t[p].n+t[t[p].son[0]].c+t[t[p].son[1]].c;}
void add(int d,int f)
{
	t[++len]=(node){d,1,1,{0,0},f};
	t[f].son[d>t[f].d]=len;
}
void rotate(int p,int w)
{
	int f=t[p].f,gf=t[f].f;
	int r=t[p].son[w],R=f;t[R].son[w^1]=r;t[r].f=R;
	r=p;R=gf;t[R].son[0]==f?t[R].son[0]=r:t[R].son[1]=r;t[r].f=R;
	r=f;R=p;t[R].son[w]=r;t[r].f=R;update(f),update(p);
}
void splay(int p,int rt)
{
	while(t[p].f!=rt)
	{
		int f=t[p].f,gf=t[f].f;
		if(gf==rt)rotate(p,t[f].son[0]==p);
		else
		{
			if(t[f].son[0]==p&&t[gf].son[0]==f)rotate(f,1),rotate(p,1);
			else if(t[f].son[1]==p&&t[gf].son[0]==f)rotate(p,0),rotate(p,1);
			else if(t[f].son[0]==p&&t[gf].son[1]==f)rotate(p,1),rotate(p,0);
			else rotate(f,0),rotate(p,0);
		}
	}
	if(!rt)root=p;
}
int get_id(int d)
{
	int p=root;
	while(t[p].d!=d)
	{
		if(d<t[p].d)
		{
			if(!t[p].son[0])break;
			p=t[p].son[0];
		}
		if(d>t[p].d)
		{
			if(!t[p].son[1])break;
			p=t[p].son[1];
		}
	}
	return p;
}
void ins(int d)
{
	if(!root){add(d,0);root=len;return;}
	int p=get_id(d);
	if(t[p].d==d)++t[p].n,update(p),splay(p,0);
	else add(d,p),update(p),splay(len,0);
}
void del(int d)
{
	int p=get_id(d);splay(p,0);
	if(t[p].n>1){--t[p].n;update(p);return ;}
	if(!t[p].son[0]&&!t[p].son[1])root=0,len=0;
	else if(t[p].son[0]&&!t[p].son[1])root=t[p].son[0],t[root].f=0;
	else if(!t[p].son[0]&&t[p].son[1])root=t[p].son[1],t[root].f=0;
	else 
	{
		int R=t[p].son[0];while(t[R].son[1])R=t[R].son[1];splay(R,p);
		int r=t[p].son[1];t[R].son[1]=r;t[r].f=R;root=R;t[root].f=0;
		splay(R,0);
	}
}
int rk(int d)
{
	int p=get_id(d);splay(p,0);
	return t[t[p].son[0]].c+1;
}
int kth(int k)
{
	int p=root;
	while(233)
	{
		int lc=t[p].son[0],rc=t[p].son[1];
		if(k<=t[lc].c)p=lc;
		else if(k>t[lc].c+t[p].n)k-=t[lc].c+t[p].n,p=rc;
		else break;
	}
	return t[p].d;
}
int prv(int d)
{
	int p=get_id(d);splay(p,0);
	if(d<=t[p].d&&t[p].son[0])//若d<t[p].d,则说明d不存在于平衡树中,t[p].d为在比d大的情况下,最接近d的数 
		for(p=t[p].son[0];t[p].son[1];p=t[p].son[1]);
	if(d<=t[p].d)p=0;//没有左孩子 
	return t[p].d;
}
int nxt(int d)
{
	int p=get_id(d);splay(p,0);
	if(d>=t[p].d&&t[p].son[1])
		for(p=t[p].son[1];t[p].son[0];p=t[p].son[0]);
	if(d>=t[p].d)p=0;//没有右孩子 
	return t[p].d;
}
int main()
{
	int n;qr(n);
	for(int i=1;i<=n;i++)
	{
		int cz,x;qr(cz),qr(x);
		if(cz==1)ins(x);
		else if(cz==2)del(x);
		else if(cz==3)qw(rk(x)),puts("");
		else if(cz==4)qw(kth(x)),puts("");
		else if(cz==5)qw(prv(x)),puts("");
		else qw(nxt(x)),puts("");
	}
	return 0;
}

文艺平衡树

关于区间翻转,平衡树同样可以用,

只要满足性质

满足根存在一性质,大于左子树的同一性质,小于右子树的同一性质

这里这个性质也就是每一个数在序列的下标,下标保持二叉搜索树结构,就可以用 s p l a y splay 进行维护,

经过观察发现,当要翻转的数,处于同一棵子树,且子树中全为要翻转的数时,随意瞎搞都行。

添加两个哨兵节点 1 1 , n + 2 n+2 ,翻转操作时,我们需要用到这两个节点去翻转 [ 1 , p ] , [ p , n ] [1,p],[p,n] 区间的,注意,平衡树中的下标比序列的下标多 1 1

如何翻转使得一个区间内的数处于同一棵子树呢?

比如我们需要翻转区间 [ l , r ] [l,r] ,需要使 [ l + 1 , r + 1 ] [l+1,r+1] 处于同一棵子树,可以把平衡树中第 l l 小的数(相当于序列中位置 l 1 l-1 )调到根上,再将第 r + 2 r+2 小的数(相当于序列中位置 r + 1 r+1 ,此时,区间 [ l , r ] [l,r] 即为 r + 2 r+2 的左子树,为什么?平衡树的性质啊!

接着我们进行打标记,顺便在翻转时懒惰操作一下。

AC code

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#define gc getchar()
using namespace std;
const int N=1e5+10;
inline void qr(int &x)
{
	x=0;int f=1;char c=gc;
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
	x*=f;
}
void qw(int x)
{
	if(x<0)x=-x,putchar('-');
	if(x/10)qw(x/10);
	putchar(x%10+48);
}
struct node{int d,son[2],f,c;bool v;}t[N];int len,root;
void update(int p){int l=t[p].son[0],r=t[p].son[1];t[p].c=t[l].c+t[r].c+1;}
void build(int &p,int f,int l,int r)
{
	if(l>r){p=0;return ;}
	int mid=(l+r)>>1;p=++len;t[p].f=f;t[p].d=mid,t[p].c=1;
	build(t[p].son[0],p,l,mid-1);
	build(t[p].son[1],p,mid+1,r);
	update(p);
}
void rotate(int p,int w)
{
	int f=t[p].f,gf=t[f].f;
	int r=t[p].son[w],R=f;t[R].son[w^1]=r;if(r)t[r].f=R;
	r=p;R=gf;t[R].son[0]==f?t[R].son[0]=r:t[R].son[1]=r;t[r].f=R;
	r=f;R=p;t[R].son[w]=r;t[r].f=R;update(f);update(p);
}
void splay(int p,int rt)
{
	while(t[p].f!=rt)
	{
		int f=t[p].f,gf=t[f].f;
		if(gf==rt)t[f].son[0]==p?rotate(p,1):rotate(p,0);
		else
		{
			if(t[f].son[0]==p&&t[gf].son[0]==f)rotate(f,1),rotate(p,1);
			else if(t[f].son[0]==p&&t[gf].son[1]==f)rotate(p,1),rotate(p,0);
			else if(t[f].son[1]==p&&t[gf].son[0]==f)rotate(p,0),rotate(p,1);
			else rotate(f,0),rotate(p,0);
		}
	}
	if(!rt)root=p;
}
int pushdown(int p)
{
	if(t[p].v)
	{
		int &lc=t[p].son[0],&rc=t[p].son[1];
		swap(lc,rc);t[p].v=0;t[lc].v^=1;t[rc].v^=1;
	}
}
int rkc(int k)
{
	int p=root;
	while(1)
	{
		pushdown(p);
		int lc=t[p].son[0],rc=t[p].son[1];
		if(k<=t[lc].c)p=lc;
		else if(k>t[lc].c+1)k-=t[lc].c+1,p=rc;
		else break;
	}
	return p;
}
void work(int l,int r)
{
	l=rkc(l),r=rkc(r+2);
	splay(l,0),splay(r,l);
	t[t[r].son[0]].v^=1;
}
void print(int p)
{
	if(!p)return ;
	pushdown(p);
	print(t[p].son[0]);
	if(t[p].d)qw(t[p].d),putchar(' ');
	print(t[p].son[1]);
}
int main()
{
	int n,m,l,r;qr(n),qr(m);
	len=0;build(t[0].son[0],0,0,n+1);root=t[0].son[0];t[len].d=0;
	for(int i=1;i<=m;i++){qr(l),qr(r);work(l,r);}
	print(root);puts("");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zyszlb2003/article/details/95312018