Treap的知识

10 篇文章 0 订阅
4 篇文章 0 订阅

upd on 2020/1/28

旋转Treap

·什么是BST?

Binary Search Tree 二叉搜索树

性质:

根节点的值大于左子树的值,小于右子树的值。

好处:
1.搜索作用

寻找k,如果比根大,在右子树,否则在左子树。

2.划分(同1)
3.同样的N的数字,由于插入BST的顺序不同,会导致树的形态不同。

·旋转平衡树(Splay会用)

旋转

例子:

·非旋转平衡树(FHQ-Treap)

什么是Treap

众所周知,Treap = tree + heap

1.heap(堆)

嗯?堆不是可以用priority_queue吗?

但是在Treap里,我们只能手工实现堆QWQ

对于堆来说,他是一个完全二叉树

大/小根堆:根节点大/小于左右子树节点值

代码。。木有

1.5.为毛要用heap?

完全二叉树啊喂!!!

深度最低啊喂!!!

然鹅。。。

BST和heap在树的形态上是冲突的。

☞怎么解决?

Treap必须是一棵BST,heap只是一种辅助。

∴我们可以随机给每个节点一个值,用作heap。

那每个节点要有什么值呢?

(1)左儿子、右儿子

(2)爸爸

(3)子树节点数

(4)自己的值

(5)随机值

例题

loj #104 普通平衡树

然后。。。你可以再交一次P3369

非旋Treap

1.非旋Treap是什么?

又称fhq-Treap,非旋转,操作多,常数小。

有两种操作。

2.split(分裂)& merge(合并)

实际上,学习FHQTreap,你只需要会这两个操作就行了

split:将一个BST分成两个BST

(1)按value值分裂(分成两棵树,其中一棵上的节点都小于等于k)

(2)按sz值分裂(分成两棵树,其中一颗sz值为k)

merge:将两个BST合成一个BST

例题:loj #105 文艺平衡树 loj #104 普通平衡树

然后。。。你可以再交一次P3391

代码码:(P3369)

#include<iostream>
#include<ctime>
#include<cstdlib>
using namespace std;

int n,root;
int siz[100010],num[100010],rnd[100010];
int x,y,z;
int cnt;

struct Node
{
	int l,r;
}s[100010];

void upd(int a)
{
	siz[a] = siz[s[a].l] + siz[s[a].r] + 1;
}

void split(int now,int k,int &x,int &y)
{
	if (!now)
	{
		x = y = 0;
	}
	else if (num[now] <= k)
	{
		x = now;
		split(s[now].r,k,s[now].r,y);
		upd(now);
	}
	else
	{
		y = now;
		split(s[now].l,k,x,s[now].l);
		upd(now);
	}
}//分裂函数

int merge(int ta,int tb)
{
	if (!ta && !tb) return 0;
	if (!tb) return ta;
	if (!ta) return tb;
	if (rnd[ta] < rnd[tb])
	{
		s[ta].r = merge(s[ta].r,tb);
		upd(ta);
		return ta;
	}
	else
	{
		s[tb].l = merge(ta,s[tb].l);
		upd(tb);
		return tb;
	}
}//合并函数

void ins(int a)
{
	split(root,a,x,y);//split可以是原树保持平衡
	siz[++cnt] = 1;
	num[cnt] = a;
	rnd[cnt] = rand();
	root = merge(merge(x,cnt),y);
}//新增节点函数

void del(int a)
{
	split(root,a,x,z);
	split(x,a - 1,x,y);
    //这里注意一个问题,我们并没有算重复数字在一个节点里,所以sz[y] > 1
	root = merge(merge(x,merge(s[y].l,s[y].r)),z);
}//删除节点函数

void rnk(int a)
{
	//这个函数的本质是分裂出一棵所有节点都 <= a的一棵树,那么这棵树的大小+1就是a的排名
	split(root,a - 1,x,y);
	cout << siz[x] + 1 << endl;
	root = merge(x,y);
}//查询排名函数

int fnd(int a,int now)
{
	//从root开始,若a <= now左子树的sz,则进入左子树;否则进入右子树,且把查询的a减去左子树的sz再减1
	while (1)
	{
		if (siz[s[now].l] >= a) now = s[now].l;
		else if (siz[s[now].l] + 1 == a)
		{
			return now;
		}
		else
		{
			a -= siz[s[now].l] + 1;
			now = s[now].r;
		}
	}
}//由排名找节点函数

void frnt(int a)
{
	//分裂出value <= a - 1的一棵树,这棵树的根(第x名)即为a的前驱
	split(root,a - 1,x,y);
	//cout << fnd(siz[x],x) << endl;
	cout << num[fnd(siz[x],x)] << endl;
	root = merge(x,y);
}//找前驱函数

void bck(int a)
{
	//分裂出value > a的一棵树,这棵树的根(第一名)即为a的后继
	split(root,a,x,y);
	cout << num[fnd(1,y)] << endl;
	root = merge(x,y);
}//找后继函数

int main()
{
	srand((unsigned)time(NULL));
	cin >> n;
	for (int i = 1;i <= n;i ++)
	{
		int opt,a;
		cin >> opt >> a;
		if (opt == 1)
		{
			ins(a);
		}
		else if (opt == 2)
		{
			del(a);
		}
		else if (opt == 3)
		{
			rnk(a);
		}
		else if (opt == 4)
		{
			cout << num[fnd(a,root)] << endl;
		}
		else if (opt == 5)
		{
			frnt(a);
		}
		else if (opt == 6)
		{
			bck(a);
		}
	}
}

具体思路见下面的那个博客(吹爆)

好的博客:Chanis/fhq-treap

upd on 2020/1/29

文艺平衡树

题目

首先,为什么区间翻转能用FHQ-Treap捏?

For instance,

翻转1-3,我们可以分裂出一颗子树(u),其sz == 3

之后直接翻转这棵树,就可以了

怎么翻转呢?

首先,我们知道从小到大是中序遍历

那么从大到小呢?

其实很简单,右中左就行了

我们可以打一个标记(fl),代表他翻转了

可是,如果对一棵被包含的子树(v)翻转呢?

标记下传!

代码可参考:Chanis/fhq-treap

(本文同步发表于洛谷博客 在luogu查看

未完待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值