NOIP--OI省选算法之三大平衡树(Treap + Splay + SBT)

Treap树

核心是 利用随机数的二叉排序树的各种操作复杂度平均为O(lgn)

Treap模板:

#include <cstdio>

#include <cstring>

#include <ctime>

#include <iostream>

#include <algorithm>

#include <cstdlib>

#include <cmath>

#include <utility>

#include <vector>

#include <queue>

#include <map>

#include <set>

#define max(x,y) ((x)>(y)?(x):(y))

#define min(x,y) ((x)>(y)?(y):(x))

#define INF 0x3f3f3f3f

#define MAXN 100005

using namespace std;

int cnt=1,rt=0; //节点编号从1开始struct Tree { int key, size, pri, son[2]; //保证父亲的pri大于儿子的pri void set(int x, int y, int z) { key=x; pri=y; size=z; son[0]=son[1]=0; } }T[MAXN];

void rotate(int p, int &x){

       int y=T[x].son[!p]; T[x].size=T[x].size-T[y].size+T[T[y].son[p]].size; T[x].son[!p]=T[y].son[p]; T[y].size=T[y].size-T[T[y].son[p]].size+T[x].size; T[y].son[p]=x; x=y; }void ins(int key, int &x){

       if(x == 0) T[x = cnt++].set(key, rand(), 1);

        else { T[x].size++;
               int p=key < T[x].key; ins(key, T[x].son[!p]);
               if(T[x].pri < T[T[x].son[!p]].pri) rotate(p, x); } }

void del(int key, int &x) //删除值为key的节点{ if(T[x].key == key) { if(T[x].son[0] && T[x].son[1]) {

int p=T[T[x].son[0]].pri > T[T[x].son[1]].pri; rotate(p, x); del(key, T[x].son[p]); } else { if(!T[x].son[0]) x=T[x].son[1];

else x=T[x].son[0]; } } else { T[x].size--; int p=T[x].key > key; del(key, T[x].son[!p]); } }int find(int p, int &x) //找出第p小的节点的编号{ if(p == T[T[x].son[0]].size+1) return x; if(p > T[T[x].son[0]].size+1) find(p-T[T[x].son[0]].size-1, T[x].son[1]); else find(p, T[x].son[0]); }int find_NoLarger(int key, int &x) //找出值小于等于key的节点个数{ if(x == 0) return 0; if(T[x].key <= key) return T[T[x].son[0]].size+1+find_NoLarger(key, T[x].son[1]); else return find_NoLarger(key, T[x].son[0]); }

View Code

相关题解:

POJ 3481 treap

POJ 1442 treap

POJ 2352 treap

Splay Tree(伸展树)

核心就是 过程Splay(x, y),即将x节点转移到y节点的子节点上面(其中y是x的祖先)。

利用其中双旋的优势能够保证查询复杂度均摊为O(lgn)

一开始理解有些困难,其实实际上不做深入的理解就是,双旋的过程就是一个建立相对平衡的二叉树的一个过程。

》对于二叉树,最极端的情况就是线性插入,使得整棵二叉树退化为一条链。比如你查询链的最后一个节点,之后再次查询第一个节点。

1)若只是单旋通过Splay(x, 0)将最后一个节点移动到根节点,需要O(n)复杂度,而查询第一个节点时又需要O(n)复杂度,来来往往就退化成一条链了。

2)若是双旋Splay(x, 0)将最后一个节点移动到根节点上时,移动过程中建立起了相对平衡的二叉树,需要O(n),也就是查询第一个节点时,大概是需要O(lgn)复杂度。这就降低了复杂度。可以证明,总的每个操作的均摊复杂度是O(lgn)。

具体证明可以参见 杨思雨《伸展树的基本操作与应用》

I 用于维护单调队列 :(以key为维护对象保证单调)

常用版:(支持相同值)

 

Struct Tree{

int key, size, fa, son[2];

}

void PushUp(int x);

void Rotate(int x, int p); //0左旋 1右旋

void Splay(int x, int To) //将x节点插入到To的子节点中

int find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处

int prev() //返回比根值小的最大值 若无返回0 若有将其转移到根处

int succ() //返回比根值大的最小值 若无返回0 若有将其转移到根处

void Insert(int key) //插入key 并且将该节点转移到根处

void Delete(int key) //删除值为key的节点 若有重点只删其中一个 x的前驱移动到根处

int GetPth(int p) //获得第p小的节点 并将其转移到根处

int GetRank(int key) //获得值<=key的节点个数 并将其转移到根处 若<key只需将<=换为<

模板:

int cnt=1, rt=0;struct Tree
{	int key, size, fa, son[2];	void set(int _key, int _size, int _fa)
	{
		key=_key;
		size=_size;
		fa=_fa;
		son[0]=son[1]=0;
	}
}T[MAXN];

inline void PushUp(int x)
{	T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+1;
}

inline void Rotate(int x, int p) //0左旋 1右旋{	int y=T[x].fa;	T[y].son[!p]=T[x].son[p];	T[T[x].son[p]].fa=y;	T[x].fa=T[y].fa;	if(T[x].fa)		T[T[x].fa].son[T[T[x].fa].son[1] == y]=x;	T[x].son[p]=y;	T[y].fa=x;
	PushUp(y);
	PushUp(x);
}void Splay(int x, int To) //将x节点插入到To的子节点中{	while(T[x].fa != To)
	{		if(T[T[x].fa].fa == To)
			Rotate(x, T[T[x].fa].son[0] == x);		else
		{			int y=T[x].fa, z=T[y].fa;			int p=(T[z].son[0] == y);			if(T[y].son[p] == x)
				Rotate(x, !p), Rotate(x, p); //之字旋
			else
				Rotate(y, p), Rotate(x, p); //一字旋		}
	}	if(To == 0) rt=x;
}int find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处{	int x=rt;	while(x && T[x].key != key)
		x=T[x].son[key > T[x].key];	if(x) Splay(x, 0);	return x;
}int prev() //返回比根值小的最大值 若无返回0 若有将其转移到根处{	int x=T[rt].son[0];	if(!x) return 0;	while(T[x].son[1])
		x=T[x].son[1];
	Splay(x, 0);	return x;
}int succ() //返回比根值大的最小值 若无返回0 若有将其转移到根处{	int x=T[rt].son[1];	if(!x) return 0;	while(T[x].son[0])
		x=T[x].son[0];
	Splay(x, 0);	return x;
}void Insert(int key) //插入key 并且将该节点转移到根处{	if(!rt)		T[rt = cnt++].set(key, 1, 0);	else
	{		int x=rt, y=0;		while(x)
		{
			y=x;
			x=T[x].son[key > T[x].key];
		}		T[x = cnt++].set(key, 1, y);		T[y].son[key > T[y].key]=x;
		Splay(x, 0);
	}
}void Delete(int key) //删除值为key的节点 若有重点只删其中一个 x的前驱移动到根处{	int x=find(key);	if(!x) return;	int y=T[x].son[0];	while(T[y].son[1])
		y=T[y].son[1];	int z=T[x].son[1];	while(T[z].son[0])
		z=T[z].son[0];	if(!y && !z)
	{
		rt=0;		return;
	}	if(!y)
	{
		Splay(z, 0);		T[z].son[0]=0;
		PushUp(z);		return;
	}	if(!z)
	{
		Splay(y, 0);		T[y].son[1]=0;
		PushUp(y);		return;
	}
	Splay(y, 0);
	Splay(z, y);	T[z].son[0]=0;
	PushUp(z);
	PushUp(y);
}int GetPth(int p) //获得第p小的节点 并将其转移到根处{	if(!rt) return 0;	int x=rt, ret=0;	while(x)
	{		if(p == T[T[x].son[0]].size+1)			break;		if(p>T[T[x].son[0]].size+1)
		{
			p-=T[T[x].son[0]].size+1;
			x=T[x].son[1];
		}		else
			x=T[x].son[0];
	}
	Splay(x, 0);	return x;
}int GetRank(int key) //获得值<=key的节点个数 并将其转移到根处 若<key只需将<=换为<{	if(!rt) return 0;	int x=rt, ret=0, y;	while(x)
	{
		y=x;		if(T[x].key <= key)
		{
			ret+=T[T[x].son[0]].size+1;
			x=T[x].son[1];
		}		else
			x=T[x].son[0];
	}
	Splay(y, 0);	return ret;
}

View Code

完全版:(支持相同值,支持区间删除,支持懒惰标记)

 

Struct Tree{

int key, num, size, fa, son[2];

}

void PushUp(int x);

void PushDown(int x);

int Newnode(int key, int fa); //新建一个节点并返回

void Rotate(int x, int p); //0左旋 1右旋

void Splay(int x, int To); //将x节点移动到To的子节点中

int GetPth(int p, int To); //返回第p小的节点 并移动到To的子节点中

int Find(int key); //返回值为key的节点 若无返回0 若有将其转移到根处

int Prev(); //返回根节点的前驱

int Succ(); //返回根结点的后继

void Insert(int key); //插入key值

void Delete(int key); //删除值为key的节点

int GetRank(int key); //获得值<=key的节点个数

void Delete(int l, int r); //删除值在[l, r]中的节点

模板:

int cnt, rt;int Add[MAXN];struct Tree{	int key, num, size, fa, son[2];
}T[MAXN];

inline void PushUp(int x)
{	T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+T[x].num;
}

inline void PushDown(int x)
{	if(Add[x])
	{		if(T[x].son[0])
		{			T[T[x].son[0]].key+=Add[x];
			Add[T[x].son[0]]+=Add[x];
		}		if(T[x].son[1])
		{			T[T[x].son[1]].key+=Add[x];
			Add[T[x].son[1]]+=Add[x];
		}
		Add[x]=0;
	}
}

inline int Newnode(int key, int fa) //新建一个节点并返回{	++cnt;	T[cnt].key=key;	T[cnt].num=T[cnt].size=1;	T[cnt].fa=fa;	T[cnt].son[0]=T[cnt].son[1]=0;	return cnt;
}

inline void Rotate(int x, int p) //0左旋 1右旋{	int y=T[x].fa;
	PushDown(y);
	PushDown(x);	T[y].son[!p]=T[x].son[p];	T[T[x].son[p]].fa=y;	T[x].fa=T[y].fa;	if(T[x].fa)		T[T[x].fa].son[T[T[x].fa].son[1] == y]=x;	T[x].son[p]=y;	T[y].fa=x;
	PushUp(y);
	PushUp(x);
}void Splay(int x, int To) //将x节点移动到To的子节点中{	while(T[x].fa != To)
	{		if(T[T[x].fa].fa == To)
			Rotate(x, T[T[x].fa].son[0] == x);		else
		{			int y=T[x].fa, z=T[y].fa;			int p=(T[z].son[0] == y);			if(T[y].son[p] == x)
				Rotate(x, !p), Rotate(x, p); //之字旋
			else
				Rotate(y, p), Rotate(x, p); //一字旋		}
	}	if(To == 0) rt=x;
}int GetPth(int p, int To) //返回第p小的节点 并移动到To的子节点中{	if(!rt || p > T[rt].size) return 0;	int x=rt;	while(x)
	{
		PushDown(x);		if(p >= T[T[x].son[0]].size+1 && p <= T[T[x].son[0]].size+T[x].num)			break;		if(p > T[T[x].son[0]].size+T[x].num)
		{
			p-=T[T[x].son[0]].size+T[x].num;
			x=T[x].son[1];
		}		else
			x=T[x].son[0];
	}
	Splay(x, 0);	return x;
}int Find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处{	if(!rt) return 0;	int x=rt;	while(x)
	{
		PushDown(x);		if(T[x].key == key) break;
		x=T[x].son[key > T[x].key];
	}	if(x) Splay(x, 0);	return x;
}int Prev() //返回根节点的前驱 非重点{	if(!rt || !T[rt].son[0]) return 0;	int x=T[rt].son[0];	while(T[x].son[1])
	{
		PushDown(x);
		x=T[x].son[1];
	}
	Splay(x, 0);	return x;
}int Succ() //返回根结点的后继 非重点{	if(!rt || !T[rt].son[1]) return 0;	int x=T[rt].son[1];	while(T[x].son[0])
	{
		PushDown(x);
		x=T[x].son[0];
	}
	Splay(x, 0);	return x;
}void Insert(int key) //插入key值{	if(!rt)
		rt=Newnode(key, 0);	else
	{		int x=rt, y=0;		while(x)
		{
			PushDown(x);
			y=x;			if(T[x].key == key)
			{				T[x].num++;				T[x].size++;				break;
			}			T[x].size++;
			x=T[x].son[key > T[x].key];
		}		if(!x)
			x=T[y].son[key > T[y].key]=Newnode(key, y);
		Splay(x, 0);
	}
}void Delete(int key) //删除值为key的节点1个{	int x=Find(key);	if(!x) return;	if(T[x].num>1)
	{		T[x].num--;
		PushUp(x);		return;
	}	int y=T[x].son[0];	while(T[y].son[1])
		y=T[y].son[1];	int z=T[x].son[1];	while(T[z].son[0])
		z=T[z].son[0];	if(!y && !z)
	{
		rt=0;		return;
	}	if(!y)
	{
		Splay(z, 0);		T[z].son[0]=0;
		PushUp(z);		return;
	}	if(!z)
	{
		Splay(y, 0);		T[y].son[1]=0;
		PushUp(y);		return;
	}
	Splay(y, 0);
	Splay(z, y);	T[z].son[0]=0;
	PushUp(z);
	PushUp(y);
}int GetRank(int key) //获得值<=key的节点个数{	if(!Find(key))
	{
		Insert(key);		int tmp=T[T[rt].son[0]].size;
		Delete(key);		return tmp;
	}	else
		return T[T[rt].son[0]].size+T[rt].num;
}void Delete(int l, int r) //删除值在[l, r]中的所有节点 l!=r{	if(!Find(l)) Insert(l);	int p=Prev();	if(!Find(r)) Insert(r);	int q=Succ();	if(!p && !q)
	{
		rt=0;		return;
	}	if(!p)
	{		T[rt].son[0]=0;
		PushUp(rt);		return;
	}	if(!q)
	{
		Splay(p, 0);		T[rt].son[1]=0;
		PushUp(rt);		return;
	}
	Splay(p, q);	T[p].son[1]=0;
	PushUp(p);
	PushUp(q);
}

View Code

(经测NOI2004郁闷的出纳员 POJ3481 POJ2352 POJ1442)

速度相对来说都还不错,POJ这些都3~500ms,郁闷的出纳员900多ms

相关题解:

HNOI 2002 营业额统计

POJ 3481 splay

POJ 2352 splay

POJ 1442 splay

NOI2004 郁闷的出纳员

II 用于维护序列: (以序列下标为对象维护,相当于对区间操作)(能够完成线段树的操作及其不能完成的操作)

 

Struct Tree{

int key, sum, size, fa, son[2];

}

支持操作:

void PushUp(int x);

void PushDown(int x);

int MakeTree(int l, int r, int a[]); //新建一个子树返回根节点

void Rotate(int x, int p); //0左旋 1右旋

void Splay(int x, int To); //将x节点移动到To的子节点中

int Select(int p, int To); //将第p个数移动到To的子节点中 并返回该节点

int Find(int key); //返回值为key的节点 若无返回0 若有将其转移到根处

int Prev(); //返回根节点的前驱

int Succ(); //返回根结点的后继

void Insert(int p, int l, int r, int a[]) //将a[l .. r]的数插入到下标为p后面

void Delete(int l, int r); //删除区间[l, r]中的节点

int Query(int l, int r); //返回[l, r]的和

待补充。。

Size Balance Tree

和上述两种二叉树比起来,SBT可能是最像真正平衡二叉树吧。

SBT能够保证树的高度在lgn,这样对于插入,删除操作都能够准确保证时间复杂度在O(lgn)

Maintain操作事实上理解起来也是挺简单的,至于证明参见CQF神牛的 《SBT》

int cnt, rt;struct Tree
{	int key, size, son[2];
}T[MAXN];

inline void PushUp(int x)
{	T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+1;
}

inline int Newnode(int key)
{	++cnt;	T[cnt].key=key;	T[cnt].size=1;	T[cnt].son[0]=T[cnt].son[1]=0;	return cnt;
}void Rotate(int p, int &x)
{	int y=T[x].son[!p];	T[x].son[!p]=T[y].son[p];	T[y].son[p]=x;
	PushUp(x);
	PushUp(y);
	x=y;
}void Maintain(int &x, int p) //维护SBT的!p子树{	if(T[T[T[x].son[p]].son[p]].size > T[T[x].son[!p]].size)
		Rotate(!p, x);	else if(T[T[T[x].son[p]].son[!p]].size > T[T[x].son[!p]].size)
		Rotate(p, T[x].son[p]), Rotate(!p, x);	else return;
	Maintain(T[x].son[0], 0);
	Maintain(T[x].son[1], 1);
	Maintain(x, 0);
	Maintain(x, 1);
}

inline int Prev() //返回比根值小的最大值 若无返回0{	int x=T[rt].son[0];	if(!x) return 0;	while(T[x].son[1])
		x=T[x].son[1];	return x;
}

inline int Succ() //返回比根值大的最小值 若无返回0{	int x=T[rt].son[1];	if(!x) return 0;	while(T[x].son[0])
		x=T[x].son[0];	return x;
}void Insert(int key, int &x)
{	if(!x) x=Newnode(key);	else
	{		T[x].size++;
		Insert(key, T[x].son[key > T[x].key]);
		Maintain(x, key > T[x].key);
	}
}bool Delete(int key, int &x) //删除值为key的节点 key可以不存在{	if(!x) return 0;	if(T[x].key == key)
	{		if(!T[x].son[0])
		{
			x=T[x].son[1];			return 1;
		}		if(!T[x].son[1])
		{
			x=T[x].son[0];			return 1;
		}		int y=Prev();		T[x].size--;		return Delete(T[x].key, T[x].son[0]);
	}	else
		if(Delete(key, T[x].son[key > T[x].key]))
		{			T[x].size--;			return 1;
		}
}int GetPth(int p, int &x) //返回第p小的节点{	if(!x) return 0;	if(p == T[T[x].son[0]].size+1)		return x;	if(p > T[T[x].son[0]].size+1)		return GetPth(p-T[T[x].son[0]].size-1, T[x].son[1]);	else
		return GetPth(p, T[x].son[0]);
}int GetRank(int key, int &x) //找出值<=key的节点个数{	if(!x) return 0;	if(T[x].key <= key)		return T[T[x].son[0]].size+1+GetRank(key, T[x].son[1]);	else
		return GetRank(key, T[x].son[0]);
}



NOIP信息学视频地址

视频地址

链接:https://pan.baidu.com/s/1tHo1DFMaDuMZAemNH60dmw 
提取码:7jgr

posted @ 2020-10-30 13:09  tianli3151  阅读(272)  评论(0编辑  收藏  举报