关于Treap
一提到维护序列、可支持分裂合并的平衡树,大家都能想到Splay。不过,实际上Treap也可以支持,而且可以无需任何旋转操作,这使得Treap具备可持久化的特点。
本蒟蒻总感觉Treap=Tree+Heap。
合并操作
我们可以模仿左偏树的合并方式。注意不能像左偏树那样交换。
void merge(int l,int r,int &x){
clear(l);
clear(r);
if (!l||!r) x=l+r;
else {
if (cmp(l,r)){
merge(tree[l][1],r,tree[l][1]);
x=l;
}
else{
merge(l,tree[r][0],tree[r][0]);
x=r;
}
}
update(x);
}
cmp是什么呢?通常来说是比较fix也就是堆关键字的,但有的时候不一样(例如鏖战表达式)。
分裂操作
我们可以像kth操作一样往下,同时拆树,然后通过迭代回来的两个树进行合并。
void split(int x,int y,int &l,int &r){
clear(x);
if (!y){
l=0;
r=x;
}
else{
if (size[tree[x][0]]>=y){
split(tree[x][0],y,l,r);
tree[x][0]=r;
update(x);
r=x;
}
else{
split(tree[x][1],y-size[tree[x][0]]-1,l,r);
tree[x][1]=l;
update(x);
l=x;
}
}
}
好处
注意到Treap每一个操作均为从上至小,我们打标记可以更加简单的处理。
几道例题
排序:
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200000+10,range=maxn*5;
int s[maxn],size[maxn],key[maxn],fix[maxn],tree[maxn][2];
int i,j,k,l,r,mid,t,n,m,tot,root;
void updata(int x){
size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
}
int find(int x,int y){
int z;
if (!x) return 0;
else if (key[x]==y) return x;
else if (key[x]<y){
z=find(tree[x][1],y);
if (!z) return x;else return z;
}
else{
z=find(tree[x][0],y);
if (z) return z;else return 0;
}
}
int rank(int x,int y){
if (!x) return 0;
if (y<key[x]) return rank(tree[x][0],y);
else return size[tree[x][0]]+1+rank(tree[x][1],y);
}
bool cmp(int x,int y){
if (fix[x]==fix[y]) return (rand()%(size[x]+size[y])<size[x]);
else return fix[x]<fix[y];
}
void merge(int l,int r,int &x){
if (!l||!r) x=l+r;
else {
if (cmp(l,r)){
merge(tree[l][1],r,tree[l][1]);
x=l;
}
else{
merge(l,tree[r][0],tree[r][0]);
x=r;
}
}
updata(x);
}
void split(int x,int y,int &l,int &r){
if (!x){
l=r=0;
}
else if (!y){
l=0;
r=x;
}
else{
if (size[tree[x][0]]>=y){
split(tree[x][0],y,l,r);
tree[x][0]=r;
updata(x);
r=x;
}
else{
split(tree[x][1],y-size[tree[x][0]]-1,l,r);
tree[x][1]=l;
updata(x);
l=x;
}
}
}
void print(int x){
if (!x) return;
print(tree[x][0]);
printf("%d\n",key[x]);
print(tree[x][1]);
}
int main(){
srand(time(0));
scanf("%d",&n);
fo(i,1,n){
scanf("%d",&key[i]);
fix[i]=rand()%1000;
}
root=1;
fo(i,2,n){
k=find(root,key[i]);
if (k) k=rank(root,key[k]);
split(root,k,l,r);
merge(l,i,l);
merge(l,r,root);
}
print(root);
}
最大值1:
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
int fix[maxn],key[maxn],num[maxn],size[maxn],tree[maxn][2];
int i,j,k,l,mid,r,t,n,m,root;
void update(int x){
size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
num[x]=key[x];
if (tree[x][0]) num[x]=max(num[x],num[tree[x][0]]);
if (tree[x][1]) num[x]=max(num[x],num[tree[x][1]]);
}
bool cmp(int x,int y){
if (fix[x]==fix[y]) return (rand()%(size[x]+size[y])<size[x]);
else return fix[x]<fix[y];
}
void merge(int l,int r,int &x){
if (!l||!r) x=l+r;
else {
if (cmp(l,r)){
merge(tree[l][1],r,tree[l][1]);
x=l;
}
else{
merge(l,tree[r][0],tree[r][0]);
x=r;
}
}
update(x);
}
void split(int x,int y,int &l,int &r){
if (!y){
l=0;
r=x;
}
else{
if (size[tree[x][0]]>=y){
split(tree[x][0],y,l,r);
tree[x][0]=r;
update(x);
r=x;
}
else{
split(tree[x][1],y-size[tree[x][0]]-1,l,r);
tree[x][1]=l;
update(x);
l=x;
}
}
}
int main(){
srand(time(0));
scanf("%d",&n);
fo(i,1,n){
scanf("%d",&key[i]);
fix[i]=rand();
update(i);
merge(root,i,root);
}
scanf("%d",&m);
while (m--){
scanf("%d%d%d",&t,&j,&k);
if (t==1){
split(root,j,l,r);
split(l,j-1,l,mid);
key[mid]=num[mid]=k;
merge(l,mid,l);
merge(l,r,root);
}
else{
split(root,k,l,r);
split(l,j-1,l,mid);
printf("%d\n",num[mid]);
merge(l,mid,l);
merge(l,r,root);
}
}
}
最大值2:
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
int fix[maxn],key[maxn],num[maxn],size[maxn],add[maxn],tree[maxn][2];
int i,j,k,l,mid,r,t,n,m,root;
void update(int x){
size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
num[x]=key[x];
if (tree[x][0]) num[x]=max(num[x],num[tree[x][0]]);
if (tree[x][1]) num[x]=max(num[x],num[tree[x][1]]);
}
void clear(int x){
if (add[x]){
if (tree[x][0]){
key[tree[x][0]]+=add[x];
num[tree[x][0]]+=add[x];
add[tree[x][0]]+=add[x];
}
if (tree[x][1]){
key[tree[x][1]]+=add[x];
num[tree[x][1]]+=add[x];
add[tree[x][1]]+=add[x];
}
add[x]=0;
}
}
bool cmp(int x,int y){
if (fix[x]==fix[y]) return (rand()%(size[x]+size[y])<size[x]);
else return fix[x]<fix[y];
}
void merge(int l,int r,int &x){
clear(l);
clear(r);
if (!l||!r) x=l+r;
else {
if (cmp(l,r)){
merge(tree[l][1],r,tree[l][1]);
x=l;
}
else{
merge(l,tree[r][0],tree[r][0]);
x=r;
}
}
update(x);
}
void split(int x,int y,int &l,int &r){
clear(x);
if (!y){
l=0;
r=x;
}
else{
if (size[tree[x][0]]>=y){
split(tree[x][0],y,l,r);
tree[x][0]=r;
update(x);
r=x;
}
else{
split(tree[x][1],y-size[tree[x][0]]-1,l,r);
tree[x][1]=l;
update(x);
l=x;
}
}
}
int main(){
srand(time(0));
scanf("%d",&n);
fo(i,1,n){
scanf("%d",&key[i]);
fix[i]=rand();
update(i);
merge(root,i,root);
}
scanf("%d",&m);
while (m--){
scanf("%d%d%d",&t,&j,&k);
if (t==1){
scanf("%d",&t);
split(root,k,l,r);
split(l,j-1,l,mid);
key[mid]+=t;
num[mid]+=t;
add[mid]+=t;
merge(l,mid,l);
merge(l,r,root);
}
else{
split(root,k,l,r);
split(l,j-1,l,mid);
printf("%d\n",num[mid]);
merge(l,mid,l);
merge(l,r,root);
}
}
}
Treap当然也能实现Splay的很多操作如插入、翻转等。
从上面两道最大值可以看出Treap可以实现线段树功能。
上文说过,这里再强调由于不需要旋转所以Treap能够可持久化。
可持久化时要用一个新操作newnode可以更加轻松!