可持久化平衡树 详解

本文介绍了可持久化平衡树的概念,它是可以维护历史版本的平衡树,特别适合于需要历史状态查询的场景。文章详细讲解了如何通过无旋treap实现可持久化平衡树,并讨论了Merge和Split操作,强调了在操作中正确处理节点复制和标记传递的重要性。文中还提供了几个例题和相应的代码示例,包括Luogu P3835和P5055,以及一个更复杂的JZOJ文本编辑器问题,展示了在不同场景下如何应用可持久化平衡树。
摘要由CSDN通过智能技术生成

前置知识: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);
	}
}

在此处是按照大小进行分裂,也可以按照权值分裂。
也是要记得 先下传标记

另外,还有非常重要的:下传标记要复制新点,而上传不需要

可以发现,可持久化和普通其实没有什么实质的区别,还是很好学的=)

例题

Luogu P3835 【模板】可持久化平衡树

非常经典的例题,普通的平衡树加上历史版本的修改和查询,若普通平衡树通过了,就可以加上历史版本维护随便切了。

代码
#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]=
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值