在上一篇题解,我们研究了FHQ实现维护有序序列与区间翻转,在这一篇题解,我们将要探讨关于FHQ实现可持久化的操作。
洛谷P3835 【模板】可持久化平衡树
由题目可得这显然必须使用可持久化,我们先了解一下什么是可持久化。
可持久化是指一个数据结构在修改操作(如插入、删除、更新)后,仍然保留其修改前的版本,并且能够同时访问修改前和修改后的所有历史版本。
他的关键特征如下:
保留历史版本:
每次修改操作不会直接覆盖原有数据,而是创建新版本。
旧版本的数据结构不会被销毁,与新版本共存。
同时访问:
程序可以随时查询或访问任何一个已保存的历史版本,就像它们从未被修改过一样。
访问历史版本的操作通常与访问当前版本的操作效率相当。
版本标识:
每个版本通常由一个唯一的标识符(如时间戳、版本号、指针)来区分。
查询操作需要指定要访问的版本标识符。
我们想要在每一次操作后生成一个新的版本,倘使我们直接将平衡树复制一遍,再进行修改,这样虽然可以做出来,但时间空间都会超爆。那我们急需新的方法。
我们发现我们再进行修改时只修改了部分平衡树,对于其他的部分,我们如何使它保留下来呢?我们可以使用操作clone克隆来实现。
所谓克隆,起始就是将一个节点的信息完整的复制一遍。具体实现如下:
lit *clone(lit *u){
if(!u)return nullptr;//空指针复制一次也是空指针
lit *p=new lit(u->val);//先重新新建一个节点
p->pri=u->pri;//复制pri,siz,l,r
p->siz=u->siz;
p->l=u->l;
p->r=u->r;
return p;
}
你可能会问,啊你这不是和 p p p节点不是和 u u u节点一模一样吗?为什么不直接用 u u u节点?因为这是指针,如果直接将它赋值给我们要的节点,我们要的节点会直接指向 u u u,进而修改的也是 u u u。
我们的分裂有一些地方需要修改。再这里我们又要提出著名论断:可持久化只要修改就要克隆。
void split(lit *u,int k,lit *&x,lit *&y){//可持久化分裂
if(!u){x=y=nullptr;return;}//空节点克隆了也是空节点
if(u->val<=k){x=clone(u);split(u->r,k,x->r,y);pushup(x);}//要修改就克隆
else {y=clone(u);split(u->l,k,x,y->l);pushup(y);}
}
我们的分裂也差不多,对于每一个要"接"的节点都要克隆。
lit *merge(lit *x,lit *y){
if(!x)return y;//边界
if(!y)return x;
if(x->pri>y->pri){x=clone(x);x->r=merge(x->r,y);pushup(x);return x;}//哪一个要接,哪一个就要复制
else {y=clone(y);y->l=merge(x,y->l);pushup(y);return y;}
}
其他操作都差不多,和原来的题一致。只需要在不同版本上进行修改即可。具体代码在下面。
#include
using namespace std;
const int N=5e5+5,maxx=2147483647,minn=-2147483647;
struct lit{
int val,pri,siz;
lit *l,*r;
lit(int _val){val=_val;siz=1;pri=rand();l=r=nullptr;}
};
lit *a[N];
class FHQ_Treap{
private:
int getsize(lit *u){return u?u->siz:0;}
void pushup(lit *&u){if(u)u->siz=1+getsize(u->l)+getsize(u->r);}
lit *clone(lit *u){
if(!u)return nullptr;
lit *p=new lit(u->val);
p->pri=u->pri;
p->siz=u->siz;
p->l=u->l;
p->r=u->r;
return p;
}
void split(lit *u,int k,lit *&x,lit *&y){
if(!u){x=y=nullptr;return;}
if(u->val<=k){x=clone(u);split(u->r,k,x->r,y);pushup(x);}
else {y=clone(u);split(u->l,k,x,y->l);pushup(y);}
}
lit *merge(lit *x,lit *y){
if(!x)return y;
if(!y)return x;
if(x->pri>y->pri){x=clone(x);x->r=merge(x->r,y);pushup(x);return x;}
else {y=clone(y);y->l=merge(x,y->l);pushup(y);return y;}
}
public:
lit *insert(lit *u,int v){
lit *x,*y;
split(u,v,x,y);
return merge(merge(x,new lit(v)),y);
}
lit *remove(lit *u,int v){
lit *l,*mid,*r;
split(u,v,mid,r);
split(mid,v-1,l,mid);
if(mid){lit *tmp=mid;mid=merge(mid->l,mid->r);delete tmp;}
return merge(merge(l,mid),r);
}
int rank(lit *u,int v){
lit *x,*y;
split(u,v-1,x,y);
return getsize(x)+1;
}
lit *kth(lit *u,int v){
if(!u)return nullptr;
int ls=getsize(u->l);
if(ls+1<v)return kth(u->r,v-ls-1);
if(ls+1>v)return kth(u->l,v);
return u;
}
lit *pre(lit *u,int v){
lit *x,*y;
split(u,v-1,x,y);
while(x&&x->r)x=x->r;
return x;
}
lit *suc(lit *u,int v){
lit *x,*y;
split(u,v,x,y);
while(y&&y->l)y=y->l;
return y;
}
}tr;
int n;
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;a[0]=nullptr;
for(int i=1;i<=n;i++){
int v,op,x;
cin>>v>>op>>x;
if(op==1){a[i]=tr.insert(a[v],x);}
if(op==2){a[i]=tr.remove(a[v],x);}
if(op==3){a[i]=a[v];cout<<tr.rank(a[i],x)<<'\n';}
if(op==4){a[i]=a[v];cout<<tr.kth(a[i],x)->val<<'\n';}
if(op==5){
a[i]=a[v];
cout<<(tr.pre(a[i],x)?tr.pre(a[i],x)->val:minn)<<'\n';
}
if(op==6){
a[i]=a[v];
cout<<(tr.suc(a[i],x)?tr.suc(a[i],x)->val:maxx)<<'\n';
}
}
return 0;
}
我们对可持久化的学习就到这里了,去尝试结合上一篇题解,去完成这道题吧。