BZOJ 3224 Treap

介绍:
我们可以看到,如果一个二叉排序树节点插入的顺序是随机的,这样我们得到的二叉排序树大多数情况下是平衡的,即使存在一些极端情况,但是这种情况发生的概率很小,所以我们可以这样建立一颗二叉排序树,而不必要像AVL那样旋转,可以证明随机顺序建立的二叉排序树在期望高度是O(logn),但是某些时候我们并不能得知所有的带插入节点,打乱以后再插入。所以我们需要一种规则来实现这种想法,并且不必要所有节点。也就是说节点是顺序输入的,我们实现这一点可以用Treap。
Treap=Tree+Heap

Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是,Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap可以并不一定是。

旋转:
Treap维护堆性质的方法用到了旋转,这里先简单地介绍一下。Treap只需要两种旋转,这样编程复杂度比Splay等就要小一些,这正是Treap的特色之一。
旋转是这样的:
treap旋转 treap旋转 
void leftrotate(int &p)
{
	int k = tr[p].rs;
	tr[p].rs = tr[k].ls;
	tr[k].ls = p;
	tr[k].s = tr[p].s;
	update(p), p = k;
}
void rightrotate(int &p)
{
	int k = tr[p].ls;
	tr[p].ls = tr[k].rs;
	tr[k].rs = p;
	tr[k].s = tr[p].s;
	update(p), p = k;
}

插入:
给节点随机分配一个优先级,先和二叉排序树的插入一样,先把要插入的点插入到一个叶子上,然后跟维护堆一样,如果当前节点的优先级比根大就旋转,如果当前节点是根的左儿子就右旋如果当前节点是根的右儿子就左旋。
我们如果把插入写成递归形式的话,只需要在递归调用完成后判断是否满足堆性质,如果不满足就旋转,实现非常容易。
由于是旋转的二叉排序树,最多进行h次(h是树的高度),插入的复杂度是log( n )的,在期望情况下,所以它的期望复杂度是 O( log( N ) );

void insert(int &p, int x)
{
	if (p == 0)
	{
		p = ++tot;
		tr[p].s = tr[p].cnt = 1;
		tr[p].val = x, tr[p].lev = rand();
		return;
	}
	tr[p].s++;
	if (tr[p].val == x) tr[p].cnt++;
	else if (tr[p].val < x)
	{
		insert(R, x);
		if (tr[R].lev < tr[p].lev) leftrotate(p);
	}
	else
	{
		insert(L, x);
		if (tr[L].lev < tr[p].lev) rightrotate(p);
	}
}

删除:

有了旋转的操作之后,Treap的删除比二叉排序树还要简单。因为Treap满足堆性质,所以我们只需要把要删除的节点旋转到叶节点上,然后直接删除就可以了。具体的方法就是每次找到优先级最大的儿子,向与其相反的方向旋转,直到那个节点被旋转到叶节点,然后直接删除。删除最多进行log( n )次旋转,期望复杂度是log( n )。

void del(int &p, int x)
{
	if (p == 0) return;
	if (tr[p].val == x)
	{
		if (tr[p].cnt > 1) tr[p].cnt--, tr[p].s--;
		else
		{
			if (R == 0 || L == 0) p = L + R;
			else if (tr[L].lev < tr[R].lev) rightrotate(p), del(p, x);
			else leftrotate(p), del(p, x);
		}
	}
	else if (x > tr[p].val) tr[p].s--, del(R, x);
	else tr[p].s--, del(L, x);
}

模板题:

3224: Tyvj 1728 普通平衡树

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 19955   Solved: 8782
[ Submit][ Status][ Discuss]

Description

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

Input

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

Output

对于操作3,4,5,6每行输出一个数,表示对应答案

Sample Input

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

Sample Output

106465
84185
492737


#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<time.h>
#define L tr[p].ls
#define R tr[p].rs
using namespace std;
const int INF = 2000000007;
const int maxm = 100005;
struct treap
{
	int ls, rs, s, cnt, lev, val;
}tr[maxm];
int tot;
void update(int &p)
{
	tr[p].s = tr[L].s + tr[R].s + tr[p].cnt;
}
void leftrotate(int &p)
{
	int k = tr[p].rs;
	tr[p].rs = tr[k].ls;
	tr[k].ls = p;
	tr[k].s = tr[p].s;
	update(p), p = k;
}
void rightrotate(int &p)
{
	int k = tr[p].ls;
	tr[p].ls = tr[k].rs;
	tr[k].rs = p;
	tr[k].s = tr[p].s;
	update(p), p = k;
}
void insert(int &p, int x)
{
	if (p == 0)
	{
		p = ++tot;
		tr[p].s = tr[p].cnt = 1;
		tr[p].val = x, tr[p].lev = rand();
		return;
	}
	tr[p].s++;
	if (tr[p].val == x) tr[p].cnt++;
	else if (tr[p].val < x)
	{
		insert(R, x);
		if (tr[R].lev < tr[p].lev) leftrotate(p);
	}
	else
	{
		insert(L, x);
		if (tr[L].lev < tr[p].lev) rightrotate(p);
	}
}
void del(int &p, int x)
{
	if (p == 0) return;
	if (tr[p].val == x)
	{
		if (tr[p].cnt > 1) tr[p].cnt--, tr[p].s--;
		else
		{
			if (R == 0 || L == 0) p = L + R;
			else if (tr[L].lev < tr[R].lev) rightrotate(p), del(p, x);
			else leftrotate(p), del(p, x);
		}
	}
	else if (x > tr[p].val) tr[p].s--, del(R, x);
	else tr[p].s--, del(L, x);
}
int find_rank(int &p, int x)
{
	if (p == 0) return 0;
	if (tr[p].val == x) return tr[L].s + 1;
	else if (tr[p].val < x) return tr[L].s + tr[p].cnt + find_rank(R, x);
	else return find_rank(L, x);
}
int find_val(int &p, int x)
{
	if (p == 0) return 0;
	if (tr[L].s >= x) return find_val(L, x);
	x -= tr[L].s;
	if (tr[p].cnt >= x) return tr[p].val;
	x -= tr[p].cnt;
	return find_val(R, x);
}
int find_pre(int &p, int x)
{
	if (p == 0) return -INF;
	if (tr[p].val < x) return max(tr[p].val, find_pre(R, x));
	else return find_pre(L, x);
}
int find_sub(int &p, int x)
{
	if (p == 0) return INF;
	if (tr[p].val > x) return min(tr[p].val, find_sub(L, x));
	else return find_sub(R, x);
}
int main()
{
	int n, i, j, k, sum, ord, x, rt = 0;
	scanf("%d", &n);
	for (i = 1;i <= n;i++)
	{
		scanf("%d%d", &ord, &x);
		if (ord == 1) insert(rt, x);
		else if (ord == 2) del(rt, x);
		else if (ord == 3) printf("%d\n", find_rank(rt, x));
		else if (ord == 4) printf("%d\n", find_val(rt, x));
		else if (ord == 5) printf("%d\n", find_pre(rt, x));
		else printf("%d\n", find_sub(rt, x));
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值