前言 二叉查找树
旋转Treap
T r e a p Treap Treap是一种平衡树,它在普通二叉查找树的基础上,给每个结点多赋予了一个属性:优先级( p r i o r i t y priority priority)。对于 T r e a p Treap Treap中的每个结点,除了它的权值满足二叉查找树的性质外,它的优先级还满足堆性质,也就是结点的优先级小于它所有孩子的优先级。
换句话说,从权值上看, T r e a p Treap Treap是一个二叉查找树;从优先级上看, T r e a p Treap Treap是一个堆。(优先级是随机 r a n d ( ) rand() rand()出来的)。
我们发现普通 B S T BST BST会不平衡是因为有序的数据会使查找路径退化成链,而随机数据使其退化的概率非常小。因此我们在 T r e a p Treap Treap中赋予的这个优先级的值采用随机生成的办法,这样 T r e a p Treap Treap的结构就趋于平衡了。因此 T r e a p Treap Treap的期望深度与快排的期望递归层数一样都是 O ( l o g n ) O(logn) O(logn) 的。
为了使
T
r
e
a
p
Treap
Treap满足性质,有时我们不可避免地要对结构进行调整,而我们调整的方式是旋转。在维护Treap的过程中,我们会出现两种旋转:左旋与右旋。
左旋一个子树,这个子树的根节点为x,则旋转后会把x变为这个子树的新根的左儿子,x的右儿子会成为子树新的根。
右旋一个子树,这个子树的根节点为x,则旋转后会把x变为这个子树的新根的右儿子,x的右儿子会成为子树新的根。
(而不是像
S
p
l
a
y
Splay
Splay一样直接转到根)
插入之后新节点可能会使 T r e a p Treap Treap不满足堆性质,那么我们就通过旋转操作不断调整,这个步骤可以通过递归来实现。
在删除时,我们首先需要在
T
r
e
a
p
Treap
Treap上走,找到需要删除的那个节点,接着我们可以利用旋转操作不停调整需要删除的这个节点在树中的位置。
若删除节点为叶节点,那么我们可以直接删除;
若它只有一个儿子, 那么我们直接让那个儿子代替这个被删除的节点即可。
否则,若删除节点左儿子的优先级小于删除节点右儿子优先级,那么我们对删除节点进行右旋,让左儿子成为新的子树的根;
反之同理。
直到它变为前两种情况。
由于
T
r
e
a
p
Treap
Treap的树高是期望
O
(
l
o
g
n
)
O(logn)
O(logn) 的,所以它各个操作的期望复杂度也是
O
(
l
o
g
n
)
O(logn)
O(logn) 。
【HNOI2004】宠物收养所
只用一棵平衡树,若宠物过多则树上记录宠物,否则是领养者,互换着用就行了。
找前驱和后继,比较绝对值,删点,统计答案,和模板差不多。
#include<bits/stdc++.h>
using namespace std;
const int N=8e4+4;
const int mod=1e6;
#define ll long long
#define lc(p) son[p][0]
#define rc(p) son[p][1]
#define inf (1ll<<50ll)
int tot,val[N],siz[N],wei[N],son[N][2];
void pushup(int p){
siz[p]=siz[lc(p)]+siz[rc(p)]+1;
}
void rotate(int &p,int t){//旋
int x=son[p][t];
son[p][t]=son[x][t^1];
son[x][t^1]=p;
pushup(p);pushup(x);
p=x;
}
void insert(int &p,int x){//插
if(p){
int t=val[p]<x;
insert(son[p][t],x);
if(wei[son[p][t]]<wei[p])rotate(p,t);//随机旋,保证深度不会太大
}
else{
p=++tot;
val[p]=x;
wei[p]=rand();
}
pushup(p);
}
void remove(int &p,int x){//删
if(x==inf||x==-inf)return;
if(val[p]==x){
if(!lc(p)&&!rc(p)){p=0;return;}
rotate(p,wei[lc(p)]<wei[rc(p)]);
remove(p,x);
}
else remove(son[p][val[p]<x],x);
pushup(p);
}
ll findpre(int p,int x){//找比自己小的最大 先驱
if(!p)return -inf;
if(val[p]<=x)return max((ll)val[p],findpre(rc(p),x));
return findpre(lc(p),x);
}
ll findsuf(int p,int x){//找比自己大的最小 后继
if(!p)return inf;
if(val[p]>=x)return min((ll)val[p],findsuf(lc(p),x));
return findsuf(rc(p),x);
}
int n,rot=0,ans=0,pet=0,peo=0,root=0;
int main(){
srand(time(0));
scanf("%d",&n);
for(int i=1;i<=n;++i){
int s,x;
scanf("%d%d",&s,&x);
if(pet){
if(!s){pet++;insert(root,x);}
else{
ll l=findpre(root,x),r=findsuf(root,x);
ll t=(x-l<=r-x)?l:r; pet--;
remove(root,t);ans=(ans+abs(x-t))%mod;
}
}
else if(peo){//已没宠物
if(s){peo++;insert(root,x);}
else{
ll l=findpre(root,x),r=findsuf(root,x);
ll t=(x-l<=r-x)?l:r; peo--;
remove(root,t);ans=(ans+abs(x-t))%mod;
}
}
else if(!pet&&!peo){
if(s==0){pet++;insert(root,x);}
else{peo++,insert(root,x);}
}
}
printf("%lld",ans);
return 0;
}
【HNOI2002营业额统计】
比上一道题还简单,只用求前驱和后继,统计一下即可。
#include<bits/stdc++.h>
using namespace std;
#define lc(p) son[p][0]
#define rc(p) son[p][1]
const int N=(1<<16);
const int inf=2e9;
int tot,val[N],siz[N],wei[N],son[N][2];
int n;
void pushup(int p){
siz[p]=siz[lc(p)]+siz[rc(p)]+1;
}
void rotato(int &p,int t){
int x=son[p][t];
son[p][t]=son[x][t^1];
son[x][t^1]=p;
pushup(p);pushup(x);
p=x;
}
void insert(int &p,int x){//进入就已经是根节点了,返回也是根节点
if(p){
int t=val[p]<x;
insert(son[p][t],x);
if(wei[son[p][t]]<wei[p])rotato(p,t);
}
else{
p=++tot;
val[p]=x;
wei[p]=rand();
}
pushup(p);
}
int findpre(int p,int x){
if(!p)return -inf;
if(val[p]<=x)return max(val[p],findpre(rc(p),x));
return findpre(lc(p),x);
}
int findsuf(int p,int x){
if(!p)return inf;
if(x<=val[p])return min(val[p],findsuf(lc(p),x));
return findsuf(rc(p),x);
}
int main(){
srand(time(0));
scanf("%d",&n);
int x,e,f,p=0;
scanf("%d",&x);
long long ans=x;
insert(p,x);
for(int i=2;i<=n;i++){
scanf("%d",&x);
e=findpre(p,x);
f=findsuf(p,x);
ans+=min(abs(x-e),abs(f-x));
insert(p,x);
}
printf("%lld",ans);
return 0;
}
非旋Treap
主要是 m e r g e merge merge和 s p l i t split split两个操作。
【普通平衡树】
一个一个实现就好了。
#include<bits/stdc++.h>
using namespace std;
#define lc son[p][0]
#define rc son[p][1]
const int N=1e5+5;
int n,tot;
int wei[N],son[N][2],siz[N],val[N];
struct treap{
void pushup(int p){
siz[p]=siz[lc]+siz[rc]+1;
}
int newnode(int x){
siz[++tot]=1;val[tot]=x;wei[tot]=rand();
return tot;
}
int merge(int a,int b){
if(!a||!b)return a+b;
if(wei[a]<wei[b]){
son[a][1]=merge(son[a][1],b);
pushup(a);return a;
}
else{
son[b][0]=merge(a,son[b][0]);
pushup(b);return b;
}
}
void split(int p,int k,int &x,int &y){
if(!p)x=y=0;//叶子节点 /
else{
if(k<val[p]){y=p;split(lc,k,x,lc);}///
else{x=p;split(rc,k,rc,y);}
pushup(p);
}
}
int kth(int p,int k){
while(1){
if(k<=siz[lc])p=lc;
else if(k==siz[lc]+1)return p;
else{k-=siz[lc]+1;p=rc;}
}
return 0;
}
void insert(int &rt,int a){
int x=0,y=0;
split(rt,a,x,y);
rt=merge(merge(x,newnode(a)),y);
}
void deletee(int &rt,int a){
int x=0,y=0,z=0,p;
split(rt,a,x,z);split(x,a-1,x,y);
p=y;
y=merge(lc,rc);//合并a的左右儿子,a就被删除
rt=merge(merge(x,y),z);
}
void rank(int &rt,int a){
int x=0,y=0;
split(rt,a-1,x,y);
printf("%d\n",siz[x]+1);
rt=merge(x,y);
}
void findpre(int &rt,int a){
int x=0,y=0;
split(rt,a-1,x,y);
printf("%d\n",val[kth(x,siz[x])]);
rt=merge(x,y);
}
void findsuf(int &rt,int a){
int x=0,y=0;
split(rt,a,x,y);
printf("%d\n",val[kth(y,1)]);
rt=merge(x,y);
}
}T;
int main(){
srand(time(0));
scanf("%d",&n);
int rt=0;
for(int i=1;i<=n;i++){
int fl,a;
scanf("%d%d",&fl,&a);
if(fl==1)T.insert(rt,a);
if(fl==2)T.deletee(rt,a);
if(fl==3)T.rank(rt,a);
if(fl==4)printf("%d\n",val[T.kth(rt,a)]);
if(fl==5)T.findpre(rt,a);
if(fl==6)T.findsuf(rt,a);
}
return 0;
}