FHQ无旋平衡树可持久化详解

引入

在上一篇题解,我们研究了FHQ实现维护有序序列区间翻转,在这一篇题解,我们将要探讨关于FHQ实现可持久化的操作。

例题

洛谷P3835 【模板】可持久化平衡树
由题目可得这显然必须使用可持久化,我们先了解一下什么是可持久化。

可持久化定义

可持久化是指一个数据结构在修改操作(如插入、删除、更新)后,仍然保留其修改前的版本,并且能够同时访问修改前和修改后的所有历史版本。
他的关键特征如下:

  1. 保留历史版本
    每次修改操作不会直接覆盖原有数据,而是创建新版本。
    旧版本的数据结构不会被销毁,与新版本共存。

  2. 同时访问
    程序可以随时查询或访问任何一个已保存的历史版本,就像它们从未被修改过一样。
    访问历史版本的操作通常与访问当前版本的操作效率相当。

  3. 版本标识
    每个版本通常由一个唯一的标识符(如时间戳、版本号、指针)来区分。
    查询操作需要指定要访问的版本标识符。

具体实现

实现方法

我们想要在每一次操作后生成一个新的版本,倘使我们直接将平衡树复制一遍,再进行修改,这样虽然可以做出来,但时间空间都会超爆。那我们急需新的方法。
我们发现我们再进行修改时只修改了部分平衡树,对于其他的部分,我们如何使它保留下来呢?我们可以使用操作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

split

我们的分裂有一些地方需要修改。再这里我们又要提出著名论断:可持久化只要修改就要克隆

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);}
}

merge

我们的分裂也差不多,对于每一个要""的节点都要克隆。

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;}
	}

其他

其他操作都差不多,和原来的题一致。只需要在不同版本上进行修改即可。具体代码在下面。

code

#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;
}

总结

我们对可持久化的学习就到这里了,去尝试结合上一篇题解,去完成这道题吧。

你可能感兴趣的:(线段树/平衡树,FHQ,Treap,平衡树,数据结构,可持久化)