splay的入门

8 篇文章 0 订阅
6 篇文章 0 订阅

splay玄学,神奇,多变,应用广。均摊时间复杂度O(n log n) (不会证明,好像都是这么说“可以证明”的,单次最坏情况是O(n),但是平均下来是n log n).

思路很简单,基于rotate操作,和splay把某个点旋到那个节点之下。

几乎所有的操作都需要splay.


花了好长时间研究怎么写更简便,之后总结出了属于自己的参考模板。

(功能不全,基于bzoj1588的)

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1588

开始尝试结构体数组版本,发现写起来手残啊,现改用指针版本,较为方便。


必备知识:splay的性质,左旋右旋,zigzag和zigzig.

splay也是一种平衡树,是相当于科学的红黑树的替代品(红黑树码量太长了),效率波动接近于treap(treap要快一点,是用堆维护的,但是应用有局限性(当然不是平衡树一类))


splay性质:稳定维护的树的中序遍历不变,且中序遍历对应原数组排列。每个节点的左子树的所有元素一定比这个节点元素小,右子树所有元素比其大(不考虑重复元素)。

左旋右旋:左右旋不改变这棵树的平衡性,将一个节点旋到它父节点的位置,且一般需要考虑3个节点(子,父,祖节点).

zigzag和zigzig:我们发现3个节点在一条线上的时候,把最下面的连续旋两次又会变成单链,这样下次访问的时候就很慢,为了保证效率,我们需要使它的高度尽可能小,因此,在不断的总结中我们发现,每次考虑连续两次旋转,可以降低整棵树高度。

不清楚思路的个人建议参考:http://blog.csdn.net/leolin_/article/details/6436037


目前必须掌握的是rotate和splay.

基于bzoj还需要插入和寻找前驱和后继。(根据树的平衡型,前把某个点splay到根,前驱就是左子树的最右节点,后继就是右子树的最左节点)。


初始化:

struct node
{
	node *f;
	node *ch[2];
	int v;
	node()
	{
		f=ch[0]=ch[1]=NULL;
		v=0;
	}
}S[maxn];
node *root;
S[]数组是为这棵树静态申请的地址空间,每次新建节点就放一个位置。


rotate(双旋合一,理解的时候需要假设情况,假设左旋右旋)

void rotate(node *u)
{
	node *f=u->f;
	if(f==NULL)return ;
	int d=u==f->ch[1];
	node *ff=f->f;
	int dd=0;
	if(ff!=NULL)dd=f==ff->ch[1];//bug
	
	f->ch[d]=u->ch[d^1];
	if(u->ch[d^1]!=NULL)u->ch[d^1]->f=f;
	
	u->ch[d^1]=f;
	f->f=u;
	
	u->f=ff;
	if(ff!=NULL)ff->ch[dd]=u;
}

splay: 每次考虑连续两次旋转(除了最后一次),下面这个是考虑把u选到p下面

void splay(node *u,node *p)//把u旋转到p的下面
{
	while(u->f!=p)
	{
		node *f=u->f;
		node *ff=f->f;
		if(ff==p)
		{
			rotate(u);
			break;
		}
		int d=u==f->ch[1];
		int dd=f==ff->ch[1];
		if(d==dd)rotate(f);
		else rotate(u);
		rotate(u);
	}
	if(p==NULL)root=u;
} 

插入:当插入一个元素的时候,我们需要找到合适的位置(空树特判),比当前节点小就看该节点的左儿子是否存在,存在继续找,不存在建立新节点;右边是一样的道理。

(切记,插入之后把新节点旋到根,不要问我为什么,这就是splay的玄学之处,不这样做在一些题中很可能超时)

void insert(int key)
{
	if(root==NULL)//空树
	{
		root=&S[++ncnt];
		root->v=key;
		return ;
	} 
	node *x=root;
	node *y;
	while(1)
	{
		//分向左找和向右找
		if(key<x->v) 
		{
			if(x->ch[0])x=x->ch[0];
			else//插入 
			{
				y=&S[++ncnt];
				y->v=key;
				y->f=x;
				x->ch[0]=y;
				break;
			}
		}
		else
		{
			if(x->ch[1])x=x->ch[1];
			else
			{
				y=&S[++ncnt];
				y->v=key;
				y->f=x;
				x->ch[1]=y;
				break;
			}
		}
	}
	splay(y,NULL);
}

前后继不多说了,相信不成问题。

如果放心不下,你可以遍历这棵树检查。


最后,附上AC代码:

#include<cstdio>
#include<queue>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cmath>
#define maxn 100000+20 
using namespace std;
int n; 
int ncnt=0;
int ans=0;
struct node
{
	node *f;
	node *ch[2];
	int v;
	node()
	{
		f=ch[0]=ch[1]=NULL;
		v=0;
	}
}S[maxn];
node *root;

void rotate(node *u)
{
	node *f=u->f;
	if(f==NULL)return ;
	int d=u==f->ch[1];
	node *ff=f->f;
	int dd=0;
	if(ff!=NULL)dd=f==ff->ch[1];//bug
	
	f->ch[d]=u->ch[d^1];
	if(u->ch[d^1]!=NULL)u->ch[d^1]->f=f;
	
	u->ch[d^1]=f;
	f->f=u;
	
	u->f=ff;
	if(ff!=NULL)ff->ch[dd]=u;
}
void splay(node *u,node *p)//把u旋转到p的下面
{
	while(u->f!=p)
	{
		node *f=u->f;
		node *ff=f->f;
		if(ff==p)
		{
			rotate(u);
			break;
		}
		int d=u==f->ch[1];
		int dd=f==ff->ch[1];
		if(d==dd)rotate(f);
		else rotate(u);
		rotate(u);
	}
	if(p==NULL)root=u;
} 
//旋到根再访问 
void insert(int key)
{
	if(root==NULL)//空树
	{
		root=&S[++ncnt];
		root->v=key;
		return ;
	} 
	node *x=root;
	node *y;
	while(1)
	{
		//分向左找和向右找
		if(key<x->v) 
		{
			if(x->ch[0])x=x->ch[0];
			else//插入 
			{
				y=&S[++ncnt];
				y->v=key;
				y->f=x;
				x->ch[0]=y;
				break;
			}
		}
		else
		{
			if(x->ch[1])x=x->ch[1];
			else
			{
				y=&S[++ncnt];
				y->v=key;
				y->f=x;
				x->ch[1]=y;
				break;
			}
		}
	}
	splay(y,NULL);
}
int qian(node *u)//找u的前驱
{
	node *x=u->ch[0];
	if(x==NULL)return 0x3f3f3f3f;
	while(x->ch[1]!=NULL)x=x->ch[1];
	return x->v;
} 
int hou(node *u)
{
	node *x=u->ch[1];
	if(x==NULL)return 0x3f3f3f3f;
	while(x->ch[0]!=NULL)x=x->ch[0];
	return x->v;
}
void dfs(node *u)
{
	printf("%d ",u->v);
	if(u->ch[0])dfs(u->ch[0]);
	if(u->ch[1])dfs(u->ch[1]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;
		if(scanf("%d",&x)==EOF)x=0;
		insert(x);//旋到根
		int ll=qian(root);
		int rr=hou(root);
		if(ll==0x3f3f3f3f&&rr==0x3f3f3f3f)ans+=x;
		else ans+=min(abs(ll-x),abs(rr-x)); 
		//if(i==5)dfs(root);
	}
	printf("%d\n",ans);
	return 0;
}

本人不才,蒟蒻一枚,希望早日熟练掌握splay.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值