前言
已经是从入门到放弃的第四篇了。
但是本文并不打算给大家讲无旋Treap复杂度证明一类的。
很显然每次操作都是期望 Olog(n)的
什么是Treap?
Treap=Tree+heap
其核心思想在于在权值上维护一棵二叉查找树,在优先级上维护一个堆
有旋treap利用旋转操作来维护堆的性质,
而无旋treap利用有序构树维护堆的性质。
无旋Treap的两大构树顺序:权值与原排列
权值构树是很常见的一种构树方法。和有旋treap一样,左儿子与右儿子分别表示比自己小或比自己大的树,同时其优先级均低于该节点。这类问题用有旋treap也能够很好地解决。这样的题有很多,比如bzoj3224普通平衡树
但很不幸的是,很多与平衡树沾边的题目大多有维护一个原有有序序列的要求,这个要求基本上就把有旋treap干掉了。但是对于无旋treap,我们可以按原有的序列进行构树,这样就可以维护一个原有的有序排列了。
无旋treap的核心操作:split与merge
但是在构树之后肯定是有修改操作的。这点是毋庸置疑的。对于有旋treap,我们可以通过旋转来插入要加入的权值或是删除对应的节点,但对于可能需要进行区间操作的无旋treap,我们显然不能直接这样做。
此时,因为无旋treap的有序性,我们可以像一般的有旋treap一样对需要
插入/删除的部分进行定位查找。
如果是对于权值有序,像普通的有旋treap一样直接递归查找即可,
如果是对于原序列有序,则维护一组指针,也可以很方便地进行查询。
那么在查询到了相应的树中位置之后,我们需要做的就是——
把这棵树拆开
split
整个无旋treap的核心就是它的有序性。
在找到了需要操作的位置后,我们可以把这棵树拆成两棵有序的树。
对于查询到的节点,我们根据该节点左儿子的大小对这个节点应该在哪棵树进行判断,然后递归处理即可。
inline D split(Treap* pos,int k){
if(pos==NULL) return D(NULL,NULL);
D y;
if(sze(pos->son[0])>=k){
y=split(pos->son[0],k);
pos->son[0]=y.second;
pos->update();
y.second=pos;
}else{
y=split(pos->son[1],k-1-sze(pos->son[0]));
pos->son[1]=y.first;
pos->update();
y.first=pos;
}
return y;
}
merge
在你进行了加入/删除操作之后,你发现你手上现在有两到三棵树了,自然我们需要将这些树合并。合并的具体操作也是递归处理,此时就可以维护无旋treap的堆性质。
但是需要注意的是,合并时两棵树的左右位置,维护的是整棵树的有序性。
inline Treap* merge(Treap* a,Treap* b){
if(!a) return b;
if(!b) return a;
if(a->weight<b->weight){
a->son[1]=merge(a->son[1],b);
a->update();
return a;
}else{
b->son[0]=merge(a,b->son[0]);
b->update();
return b;
}
}
水得如壶口瀑布一样的水题
(1)bzoj3224:普通平衡树
题面见链接 传送门
最简单最基础的权值排序外加单点修改查询
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
struct Treap{
Treap* son[2];
int weight,sze,data;
Treap(int v){
sze=1,data=v;weight=rand();
son[1]=son[0]=NULL;
return ;
}
inline void update(){
sze=1+(son[0]!=NULL?son[0]->sze:0)+(son[1]!=NULL?son[1]->sze:0);
return ;
}
}*root;
typedef pair<Treap*,Treap*>D;
inline int sze(Treap* pos){
return pos?pos->sze:0;
}
int n,ord,x;
inline Treap* merge(Treap* a,Treap* b){
if(!a) return b;
if(!b) return a;
if(a->weight<b->weight){
a->son[1]=merge(a->son[1],b);
a->update();
return a;
}else{
b->son[0]=merge(a,b->son[0]);
b->update();
return b;
}
}
inline D split(Treap* pos,int k){
if(pos==NULL) return D(NULL,NULL);
D y;
if(sze(pos->son[0])>=k){
y=split(pos->son[0],k);
pos->son[0]=y.second;
pos->update();
y.second=pos;
}else{
y=split(pos->son[1],k-1-sze(pos->son[0]));
pos->son[1]=y.first;
pos->update();
y.first=pos;
}
return y;
}
inline int getrank(Treap* pos,int x){
if(pos==NULL) return 0;
else return (pos->data>=x)?getrank(pos->son[0],x):getrank(pos->son[1],x)+1+sze(pos->son[0]);
}
inline int getkth(int k){
D x=split(root,k-1);
D y=split(x.second,1);
Treap* pos=y.first;
root=merge(x.first,merge(pos,y.second));
return pos==NULL?0:pos->data;
}
inline void insert(int d){
int k=getrank(root,d);
D x=split(root,k);
Treap* pos=new Treap(d);
root=merge(x.first,merge(pos,x.second));
return ;
}
inline void remove(int d){
int k=getrank(root,d);
D x=split(root,k);
D y=split(x.second,1);
root=merge(x.first,y.second);
return ;
}
signed main(){
n=read();
for(int i=1;i<=n;++i){
ord=read();x=read();
switch(ord){
case 1:insert(x);break;
case 2:remove(x);break;
case 3:write(getrank(root,x)+1);puts("");break;
case 4:write(getkth(x));puts("");break;
case 5:write(getkth(getrank(root,x)));puts("");break;
case 6:write(getkth(getrank(root,