从零开始的数据结构:FHQ-treap

FHQ-treap

FHQ Treap,又名无旋Treap,是基于普通的Treap实现的平衡树。
有这好东西学啥splay

二叉搜索树的性质

  • 在二叉搜索树中,每个结点都满足左子树的结点的值都小于等于自己的值,右子树的结点的值都大于自己的值,左右子树也是二叉搜索树。
  • 中序遍历二叉搜索树可以得到一个由这棵树的所有结点的值组成的有序序列。(即所有的值排序后的结果)

和普通treap一样的是,FHQ-treap也是要靠随机来保证复杂度的,不同的是,FHQ-treap是无旋的,维护以及所有的操作都是在两个基本操作分裂 split合并 merge的前提下进行。

而且我感觉只要懂这两个操作,下面都不用看了
//具体可以看注释

节点设置,以及随机种子

#include<random>//随机函数头文件
mt19937 rnd(233);//里面随便写
struct node //每一个节点
{
	int l, r;//左右儿子下标
	int val, key;//权值,以及随机保证复杂度的key
	int size;//自己以及左右儿子的大小
}tree[MAXN];
int tot;//内存池
int newnode(int val)
{
	tree[++tot].val = val;
	tree[tot].key = rnd();
	tree[tot].size = 1;
	return tot;
}

split

分裂操作又可以分成两种:

1.按照数值的大小分裂,左边全部小于等于我们输入的val,右边的权值全部大于val。
2.按照左右儿子大小分裂,左边大小等于给定值,右边是另一边。(基于这个可以实现区间操作)

画不好别看我
以下面的平衡树为例
在这里插入图片描述
然后我们输入val为2的话。

先说明下下面代码的参数:
第一个是当前递归到哪里,开始传入的时候就是树的根节点
第二个就是方法(按大小或者siz分)
第三个和第四个就是分完之后,左右子树的根节点

如下图,一开始在root,发现val大于2,那么就会向左走,同时4的右儿子就相当于不用动,因为右边肯定都大于2。
然后now到了2,val小于等于2,同理左边不用在递归,递归右边。
在这里插入图片描述

void update(int now)//更新左右size 类似线段树的push_up
{
	tree[now].size = tree[tree[now].l].size + tree[tree[now].r].size + 1;
}
void split(int now, int val, int& x, int& y)//按大小分裂
{
	if (!now)x = y = 0;//如果已经递归到底了,就返回空值
	else
	{
		if (tree[now].val <= val)//满足要求,加入左边的树
		{
			x = now;
			split(tree[now].r, val, tree[now].r, y);//继续去找右儿子,左儿子一定满足要求
		}
		else//和上面同理
		{
			y = now;
			split(tree[now].l, val, x, tree[now].l);
		}
		update(now);//别忘记更新
	}
}

merge

合并操作就很简单了,2棵子树x,y;
要保证复杂度所以写一个判断:
1.左边的右儿子和右边当前节点合并。//平衡树性质
2.右边的左儿子和左边的当前节点合并。

int merge(int x, int y)//两个要合并的树
{
	if (!x || !y)return x + y;
	if (tree[x].key > tree[y].key)//这里 >,>=,<,<=都可以 反正是随机的
	{
		tree[x].r = merge(tree[x].r, y);
		update(x); return x;
	}
	else
	{
		tree[y].l = merge(x, tree[y].l);
		update(y); return y;
	}
}

模板题

落谷P3369

其他操作

插入(只有两步)

1.先按大小拆分成两个部分,x为小于等于val,y为大于val。
2.进行两次合并就行

void ins(int val)
{
	int x, y;
	split(root, val, x, y);
	root = merge(merge(x, newnode(val)), y);
}

删除(只有四步)

1.先把树拆成一份小于等于val。
2.再把那一份拆成左边小于等于val,那就意味着y树里面只有值为val的值了。
3.然后我们就不要y的根(删除),只要左右节点(只删一个值)
4.合并回去。

void del(int val)
{
	int x, y, z;
	split(root, val, x, z);
	split(x, val - 1, x, y);
	y = merge(tree[y].l, tree[y].r);
	root = merge(merge(x, y), z);
}

两个查询的操作:

1.查询值的排名(定义:比val小的数+1)

就直接拆开小于等于val-1的树,那x树的siz就是比val小的数了

void getrank(int val)
{
	int x, y;
	split(root, val - 1, x, y);
	cout << tree[x].size + 1 << endl;
	root = merge(x, y);
}

2.查询该排名的值

拆都不用拆了,直接模拟找大小即可

void getnum(int rank)
{
	int now = root;
	while (now)
	{
		if (tree[tree[now].l].size + 1 == rank)break;
		else if (tree[tree[now].l].size >= rank)now = tree[now].l;
		else rank -= tree[tree[now].l].size + 1, now = tree[now].r;
	}
	cout << tree[now].val << endl;
}

前驱 后继

就拆成相应的树,向左或向右模拟找就行

void pre(int val)
{
	int x, y;
	split(root, val - 1, x, y);
	int now = x;
	while (tree[now].r)
	{
		now = tree[now].r;
	}
	cout << tree[now].val << endl;
	root = merge(x, y);
}
void nex(int val)
{
	int x, y;
	split(root, val, x, y);
	int now = y;
	while (tree[now].l)
	{
		now = tree[now].l;
	}
	cout << tree[now].val << endl;
	root = merge(x, y);
}

区间操作

落谷P3391

区间操作和上面的代码相差不大(不然为啥简单呢
唯一的区别就是多了一个push_down的操作(想想线段树)
还要注意拆分的时候我们按树的大小

首先我们直接按顺序插入每个数

然后我们上面这道例题是要翻转一个区间,那我们只要是计一个翻转的标记即可,然后有标记的话就交换左右子树,标记下传(都是线段树刻在dna里的操作),最后别忘记合并回来就行

void revers(int l, int r)
{
	int x, y, z;
	split(root, l - 1, x, y);
	split(y, r - l + 1, y, z);
	tree[y].tag ^= 1;
	root = merge(merge(x, y), z);
}

AC代码(纯模板)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5;
const int MAXN=5e5;
int n,m;
struct node{
	int l,r;
	int val,key;
	int size;
	int tag;
}tree[MAXN];
int tot,root;
mt19937 rnd(233);
int newnode(int val)
{
	tree[++tot].val=val;
	tree[tot].key=rnd();
	tree[tot].size=1;
	return tot;
}
void push_up(int now)
{
	tree[now].size=tree[tree[now].l].size+tree[tree[now].r].size+1;
}
void push_down(int now)
{
	if(!tree[now].tag)return;
	swap(tree[now].l,tree[now].r);
	tree[tree[now].l].tag^=1;
	tree[tree[now].r].tag^=1;
	tree[now].tag=0;
}
void split(int now,int siz,int &x,int &y)
{
	if(!now)x=y=0;
	else
	{
		push_down(now);
		if(tree[tree[now].l].size<siz)
		{
			x=now;
			split(tree[now].r,siz-tree[tree[now].l].size-1,tree[now].r,y);
		}
		else
		{
			y=now;
			split(tree[now].l,siz,x,tree[now].l);
		}
		push_up(now);
	}
}
int merge(int x,int y)
{
	if(!x||!y)return x+y;
	if(tree[x].key>tree[y].key)
	{
		push_down(x);
		tree[x].r=merge(tree[x].r,y);
		push_up(x);return x;
	}
	else
	{
		push_down(y);
		tree[y].l=merge(x,tree[y].l);
		push_up(y);return y;
	}
}
void ins(int val)
{
	int x,y;
	split(root,val,x,y);
	root=merge(merge(x,newnode(val)),y);
}
void revers(int l,int r)
{
	int x,y,z;
	split(root,l-1,x,y); 
	split(y,r-l+1,y,z); 
	tree[y].tag^=1;
	root=merge(merge(x,y),z);
}
void print(int now)
{
	if(!now)return;
	push_down(now);
	print(tree[now].l);
	cout<<tree[now].val<<" ";
	print(tree[now].r);
}
void work()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)ins(i);
	for(int i=1;i<=m;i++)
	{
		int l,r;cin>>l>>r;
		revers(l,r);
	}
	print(root);
}
int main()
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int t=1;//cin>>t;
	while(t--)work();
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值