目录
介绍
无旋treap ,又称为 非旋treap , fhq_treap ,是一个非常实用的数据结构,用来优化一般的 二叉搜索树 所解决不了的问题,大大提高了时间的效率,在解决平衡树问题的应用较广。
其中用处相同的还有 splay ,但 splay 代码码量大,本蒟蒻一年前开始学,现在连无旋treap都会了,还不会splay,但是splay是 LCT 的重要前置知识,所以两种数据结构都有学的必要性。
背景
一般的二叉搜索树 (指某节点的左子树权值均比节点权值大(或小),右子树权值均比节点小(或大)的一颗二叉树) 的主要作用是查询第 kk 大的权值或权值 xx 的排名,一般的时间复杂度为 O(n log n)O(nlogn) ,但是数据可以将它卡成一条链,此时的时间复杂度为 O(n^2)O(n2)
可以看出,当这一颗二叉搜索树的形状越扁(即 深度越小 )时,其时间复杂度就会更优,于是平衡树应运而生。
原理
无旋treap,肯定和常规数据结构(指splay)不一样,它是不用旋转的(少了毒瘤操作真开心QwQ)。
treap=tree+heap(即“树”+“堆”) 所以treap既有堆的性质,又有二叉搜索树的性质,而其中堆的性质,就是我们把树拍扁的关键。
无旋treap的核心操作有两个,分别为 分裂(split) 和 合并(merge) ,分裂就是将一棵树掰开两瓣,合并就是将两棵树合为一颗树。
声明
struct node{
int ls;//左儿子
int rs;//右儿子
int sz;//子树大小
int val;//节点权值
int id;//编号(维护堆的性质)
}tree[2000200];
Pushup
不必赘述,将左右子树的 $size$ 加起来即可
void pushup(int x)
{
tree[x].sz=tree[ls(x)].sz+tree[rs(x)].sz+1;
}
Split
假设将treap掰成根为 $a$ 和根为 $b$ 的两颗treap,其中 $a$ 子树权值小于等于(大于等于) $val$ ,$b$ 子树权值大于(小于) $val$ ,假设原treap根为 $rt$ 。
若 $tree[rt].val \le val$ 则证明 $rt$ 的左子树所有的点都可以放在 aa 左子树上,问题就变为: 将根为 $tree[rt].rs$ 的树分为根为 $tree[a].rs$ 和根为 $b$ 的树,可以递归求解。
$tree[rt].val > val$ 同理,证明 $rt$ 的右子树所有点都可以放到 $b$ 右子树上,问题变为:将根为 $tree[rt].ls$ 的树分为根为 $a$ 和根为 $tree[b].ls$ 的树,同样可以递归求解。
void split(int rt,int &x,int &y,int val)
{
if(rt==0)
{
x=0,y=0;
return;
}
if(tree[rt].val<=val)//此时将rt左子树放到x左子树上
{
x=rt;
split(rs(rt),rs(x),y,val);
}
else//此时将rt右子树放到y右子树上
{
y=rt;
split(ls(rt),x,ls(y),val);
}
pushup(rt);
}
Merge
假设将根为 $a$ 的树和根为 $b$ 的树合并成根为 $rt$ 的树,且树 $a$ 所有权值严格小于树 $b$ 。
很明显,我们要么把树 $a$ 的左子树接到 $rt$ 左边,要么把树 $b$ 的右子树接到 $rt$ 右边,同样可以递归。
我们可以按照设定的 $id$ 值(取随机数)来维护堆的性质,维护一个大根(小根)堆,判断是先放 $a$ 还是先放 $b$ 。
void merge(int &rt,int x,int y)
{
if(x==0||y==0)
{
rt=x|y;
return;
}
if(tree[x].id<tree[y].id)
{
rt=x;
merge(rs(rt),rs(x),y);
}
else
{
rt=y;
merge(ls(rt),x,ls(y));
}
pushup(rt);
}
有了核心操作,剩下各种诸如插入,删除,求第 $k$ 大的权值等操作都变得肥肠简单~~
具体的操作可以看例题
例题
P3369 【模板】普通平衡树
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
using namespace std;
struct node{
int ls,rs,sz,val,id;
}tree[200200];
int n,root,tot,opt,zlz;
unsigned int seed=1919810;
inline unsigned int rad()
{
seed=seed*(unsigned int)100000007;
return seed;
}
void pushup(int x)
{
tree[x].sz=tree[ls(x)].sz+tree[rs(x)].sz+1;
}
void split(int p,int &x,int &y,int val)
{
if(p==0)
{
x=0,y=0;
return;
}
if(tree[p].val<=val)
{
x=p;
split(rs(p),rs(x),y,val);
}
else
{
y=p;
split(ls(p),x,ls(y),val);
}
pushup(p);
}
void merge(int &p,int x,int y)
{
if(x==0||y==0)
{
p=x|y;
return;
}
if(tree[x].id<tree[y].id)
{
p=x;
merge(rs(p),rs(x),y);
}
else
{
p=y;
merge(ls(p),x,ls(y));
}
pushup(p);
}
void build(int x)
{
tot++;
ls(tot)=0,rs(tot)=0;
tree[tot].sz=1,tree[tot].val=x;
tree[tot].id=rad();
}
int find_max(int x)
{
while(rs(x))
x=rs(x);
return tree[x].val;
}
int find_min(int x)
{
while(ls(x))
x=ls(x);
return tree[x].val;
}
void ins(int x)
{
build(x);
int r1=0,r2=0,r3=tot;
split(root,r1,r2,x);
merge(r1,r1,r3);
merge(r1,r1,r2);
root=r1;
return;
}
void del(int x)
{
int r1=0,r2=0,r3=0;
split(root,r1,r2,x);
split(r1,r1,r3,x-1);
merge(r3,ls(r3),rs(r3));
merge(r1,r1,r3);
merge(r1,r1,r2);
root=r1;
return;
}
int rk1(int x)
{
int r1=0,r2=0,ans=0;
split(root,r1,r2,x-1);
ans=tree[r1].sz+1;
merge(r1,r1,r2);
root=r1;
return ans;
}
int rk2(int rt,int x)
{
if(tree[ls(rt)].sz+1==x)
return tree[rt].val;
if(tree[ls(rt)].sz<x)
return rk2(rs(rt),x-tree[ls(rt)].sz-1);
else
return rk2(ls(rt),x);
}
int lst(int x)
{
int r1=0,r2=0;
split(root,r1,r2,x-1);
int ans=find_max(r1);
merge(r1,r1,r2);
root=r1;
return ans;
}
int nxt(int x)
{
int r1=0,r2=0;
split(root,r1,r2,x);
int ans=find_min(r2);
merge(r1,r1,r2);
root=r1;
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&opt,&zlz);
if(opt==1) ins(zlz);
if(opt==2) del(zlz);
if(opt==3) printf("%d\n",rk1(zlz));
if(opt==4) printf("%d\n",rk2(root,zlz));
if(opt==5) printf("%d\n",lst(zlz));
if(opt==6) printf("%d\n",nxt(zlz));
}
}