天天看點

splay的入門

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.

繼續閱讀