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;
}
}
模板题
其他操作
插入(只有两步)
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);
}
区间操作
区间操作和上面的代码相差不大(不然为啥简单呢)
唯一的区别就是多了一个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;
}