前置知识:fhq-treap(无旋treap)
定义
可以拆成 可持久化 和 平衡树 来看,所以就是可以维护历史版本的平衡树,在此,我们的无旋treap与splay相比可以很好的进行转化(主要还是因为splay的旋转操作进行历史版本回溯比较困难),其实如果会打主席树(可持久化线段树),那么可持久化平衡树还是相当简单的,与普通的平衡树相比,就多了历史版本根的记录,以及树节点的复制而已(>_<) 。
有人要问:开那么多节点,空间不会爆炸吗。
我们平衡树的深度是理论 l o g n log n logn 的,所以每次操作最多添加 l o g log log 个节点,所以空间复杂度是 n log n n \log n nlogn 的,但是还是建议开 50 50 50 倍的空间,不然会炸(主要是无旋treap的随机运气不好会炸)
如果你不会无旋 treap:无旋treap学习笔记
操作
和普通的平衡树没什么两样:
Merge
void merge(int &rt,int x,int y){
if(!x||!y){
rt=x+y;
return;
}
if(tree[x].id>tree[y].id){
pushdown(x);//注意:先下传标记,再复制节点!!!
tot++,rt=tot;
tree[rt]=tree[x];
tree[rt].id=rd();//此处建议是再取一次随机数
merge(rs(rt),rs(x),y);
pushup(rt),pushup(x);
}
else{
pushdown(y);
tot++,rt=tot;
tree[rt]=tree[y];
tree[rt].id=rd();
merge(ls(rt),x,ls(y));
pushup(rt),pushup(y);
}
}
我们的 i d id id 为随机赋值,以保证时间及空间复杂度的正确(但是有的时候还是建议随机合成,下面有例题会解释)
注意:我们每次复制节点的时候,一定要先下传标记,不然对于一个子节点来说,此时共有两个父亲携带下传标记( r o o t root root 和 n e w r o o t newroot newroot 都有),就会造成错误。
Split
void split(int rt,int &x,int &y,int siz){
if(rt==0){
x=y=0;
return;
}
pushdown(rt);
if(tree[ls(rt)].siz<siz){
tot++,x=tot;
tree[x]=tree[rt];
split(rs(rt),rs(x),y,siz-tree[ls(rt)].siz-1);
pushup(rt),pushup(x);
}
else{
tot++,y=tot;
tree[y]=tree[rt];
split(ls(rt),x,ls(y),siz);
pushup(rt),pushup(y);
}
}
在此处是按照大小进行分裂,也可以按照权值分裂。
也是要记得 先下传标记
另外,还有非常重要的:下传标记要复制新点,而上传不需要
可以发现,可持久化和普通其实没有什么实质的区别,还是很好学的=)
例题
非常经典的例题,普通的平衡树加上历史版本的修改和查询,若普通平衡树通过了,就可以加上历史版本维护随便切了。
代码
#include<cstring>
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
using namespace std;
struct node{
int ls,rs,id,siz,val;
}tree[30000200];
int tot,n,root[500500];
unsigned int seed=127;
inline int read(){
long long num=0,f=1;
char ch=getchar();
while(!(ch>='0'&&ch<='9')){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=num*10+ch-'0';
ch=getchar();
}
return num*f;
}
inline int rd(){
return seed=seed*(unsigned int)100007;
}
int add(int val){
tot++,tree[tot].id=rd();
ls(tot)=rs(tot)=0,tree[tot].siz=1;
tree[tot].val=val;
return tot;
}
void pushup(int x){
tree[x].siz=tree[ls(x)].siz+tree[rs(x)].siz+1;
}
void merge(int &rt,int x,int y){
if(x==0||y==0){
if(!x&&!y){
rt=0;
return;
}
tot++,rt=tot;
if(x) tree[rt]=tree[x];
if(y) tree[rt]=tree[y];
return;
}
if(tree[x].id<tree[y].id){
tot++,rt=tot;
tree[rt]=