可持久化平衡树 详解

前置知识: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
#include
#include
#include
#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]=tree[x];
		merge(rs(rt),rs(x),y);
	}
	else{
		tot++,rt=tot;
		tree[rt]=tree[y];
		merge(ls(rt),x,ls(y));
	}
	pushup(rt);
}
void split(int rt,int &x,int &y,int val){
	if(rt==0){
		x=y=0;
		return;
	}
	if(tree[rt].val<=val){
		tot++,x=tot;
		tree[x]=tree[rt];
		split(rs(rt),rs(x),y,val);
		pushup(x);
	}
	else{
		tot++,y=tot;
		tree[y]=tree[rt];
		split(ls(rt),x,ls(y),val);
		pushup(y);
	}
	pushup(rt);
}
int query_kth(int rt,int x){
    if(tree[ls(rt)].siz+1==x) return tree[rt].val; 
	if(tree[ls(rt)].siz>=x) return query_kth(ls(rt),x);
	return query_kth(rs(rt),x-tree[ls(rt)].siz-1); 
}
int find_max(int rt){
	while(rs(rt)) rt=rs(rt);
	return tree[rt].val;
}
int find_min(int rt){
	while(ls(rt)) rt=ls(rt);
	return tree[rt].val;
}
int main(){
	n=read();
	for(register int i=1;i<=n;i++){
		int v=read(),opt=read(),x=read();
		root[i]=root[v];
		if(opt==1){
			int m1=add(x),m2=0;
			split(root[i],root[i],m2,x);
			merge(root[i],root[i],m1);
			merge(root[i],root[i],m2);
		}
		if(opt==2){
			int m1=0,m2=0;
			split(root[i],root[i],m2,x);
			split(root[i],root[i],m1,x-1);
			merge(m1,ls(m1),rs(m1));
			merge(root[i],root[i],m1);
			merge(root[i],root[i],m2);
		}
		if(opt==3){
			int m1=0;
			split(root[i],root[i],m1,x-1);
			printf("%d\n",tree[root[i]].siz+1);
			merge(root[i],root[i],m1);
		}
		if(opt==4)
		printf("%d\n",query_kth(root[i],x));
		if(opt==5){
			int m1=0;
			split(root[i],root[i],m1,x-1);
			if(!root[i]) printf("-2147483647\n");
			else  printf("%d\n",find_max(root[i]));
			merge(root[i],root[i],m1);
		}
		if(opt==6){
			int m1=0;
			split(root[i],root[i],m1,x);
			if(!m1) printf("2147483647\n");
			else printf("%d\n",find_min(m1));
			merge(root[i],root[i],m1);
		}
	}
}

Luogu P5055 【模板】可持久化文艺平衡树

同样的,可以参考文艺平衡树的模板题,但是注意一点:下传标记一定要复制新点,我们当前的儿子还是上一个历史版本的儿子,所以此时要将其复制一份,不然就会让上一个历史版本打上此时版本的标记,这是我们所不希望看到的,下传还是 log ⁡ \log log 的,所以不用担心空间的问题,但建议有多大开多大。

代码

#include
#include
#include
#include
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
using namespace std;
struct node{
	int ls,rs,id,siz;
	long long sum,val;
	bool rev;
}tree[30000300];
int n,root[200200],tot;
long long lastans=0;
unsigned int seed=127;
unsigned int rd(){
	return seed=seed*(unsigned int)100007;
}
inline long long 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;
}
void pushdown(int x){
	if(!tree[x].rev) return;
	int nw_ls=0,nw_rs=0;
	if(ls(x)) tot++,nw_rs=tot,tree[nw_rs]=tree[ls(x)];
	if(rs(x)) tot++,nw_ls=tot,tree[nw_ls]=tree[rs(x)];
	ls(x)=nw_ls,rs(x)=nw_rs;
	tree[ls(x)].rev^=1,tree[rs(x)].rev^=1;
	tree[x].rev=0;
	return;
}
void pushup(int x){
	tree[x].siz=tree[ls(x)].siz+tree[rs(x)].siz+1;
	tree[x].sum=tree[ls(x)].sum+tree[rs(x)].sum+tree[x].val;
}
void merge(int &rt,int x,int y){
    if(!x||!y){
    	rt=x+y;
    	return;
	} 
//	pushdown(rt);
	if(tree[x].id<tree[y].id){
		pushdown(x);
		tot++,rt=tot;
		tree[rt]=tree[x];
		merge(rs(rt),rs(x),y);
	}
	else{
		pushdown(y);
		tot++,rt=tot;
		tree[rt]=tree[y];
		merge(ls(rt),x,ls(y));
	} 
	pushup(rt);
}
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(x);
	}
	else{
		tot++,y=tot;
		tree[y]=tree[rt];
		split(ls(rt),x,ls(y),siz);
		pushup(y);
	}
	pushup(rt);
}
int add(long long val){
	tot++,ls(tot)=rs(tot)=0;
	tree[tot].siz=1,tree[tot].sum=tree[tot].val=val;
	tree[tot].rev=0,tree[tot].id=rd();
	return tot;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int v=read(),opt=read();
		root[i]=root[v];
		if(opt==1){
			int p=read(),x=read();
			p^=lastans,x^=lastans;
			int m1=0,m2=add(x);
			split(root[i],root[i],m1,p);
			merge(root[i],root[i],m2);
			merge(root[i],root[i],m1);
		}
		if(opt==2){
			int p=read();
			p^=lastans;
			int m1=0,m2=0;
			split(root[i],root[i],m1,p);
			split(root[i],root[i],m2,p-1);
			merge(root[i],root[i],m1);
		} 
		if(opt==3){
			int l=read(),r=read(),m1=0,m2=0;
			l^=lastans,r^=lastans;
			split(root[i],root[i],m1,r);
			split(root[i],root[i],m2,l-1);
			tree[m2].rev^=1;
			merge(root[i],root[i],m2);
			merge(root[i],root[i],m1);
		}
		if(opt==4){
			int l=read(),r=read(),m1=0,m2=0;
			l^=lastans,r^=lastans;
			split(root[i],root[i],m1,r);
			split(root[i],root[i],m2,l-1);
			lastans=tree[m2].sum;
			printf("%lld\n",lastans);
			merge(root[i],root[i],m2);
			merge(root[i],root[i],m1);
		}
	}
}

不带劲?来点难的。

JZOJ 【NOI2014模拟】文本编辑器(editor)

可持久化平衡树 详解_第1张图片

在这里插入图片描述
除了第三个操作以外,我们其他的操作都可以用无旋 treap 来实现,但是看到第三个操作,我们总不能一个一个加上去吧。。。(时间空间双爆炸力,悲),所以我们考虑将此区间暂时存放在第 x x x 个字符后面,这样就不用每个字符都插入一遍了,只用插入 log ⁡ \log log 个,所以时间复杂度和空间复杂度都是 O ( m log ⁡ max ⁡ ( L e n ) ) O(m \log \max(Len)) O(mlogmax(Len)) ,但是如果给 t r e a p treap treap 随机标号,我们会发现:

在这里插入图片描述
为什么?
别忘记我们无旋treap现在的长度可是 L e n Len Len 的,所以如果我们没弄好左右子树存放条件,我们可承担不起多出来的深度(毕竟是 L e n Len Len 的,悲 )

经过研究前人的代码,我发现有两种判断方法:
1. 1. 1. s i z e [ x ] > s i z e [ y ] size[x]>size[y] size[x]>size[y]
2. 2. 2. r a n d ( ) m o d    ( s i z e [ x ] + s i z e [ y ] ) < s i z e [ x ] rand()\mod (size[x]+size[y])rand()mod(size[x]+size[y])<size[x]
都只和 treap 的 s i z e size size 相关,和随机数毛关系都没有,为什么?(我也不会)
感性理解一波:我们的”随机数“在此处循环节太短,所以会出现树的深度不符期望的情况。而这种真正做到了随机合并(

第一种方法:
在这里插入图片描述
第二种方法:

在这里插入图片描述
(这就是玄学的力量吗)

代码

#include
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
using namespace std;
struct node{
	int ls,rs,siz,id;
	char c;
	bool rev;
}tree[20000100];
int tot=0,n,root;
mt19937 myrand(time(0));
unsigned int seed=0;
inline int read(){
	int 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;
}
unsigned int rd(){
	return seed=(seed*(unsigned int)3450934+(unsigned int)484524);
}
void pushdown(int x){
	if(!tree[x].rev) return;
	int nw_ls=0,nw_rs=0;
	if(ls(x)) tot++,nw_rs=tot,tree[tot]=tree[ls(x)];
	if(rs(x)) tot++,nw_ls=tot,tree[tot]=tree[rs(x)];
	ls(x)=nw_ls,rs(x)=nw_rs;
	tree[ls(x)].rev^=1,tree[rs(x)].rev^=1;
	tree[x].rev=0;
	return;
}
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||!y){
		rt=x+y;
		return;
	}
    if(tree[x].siz>tree[y].siz){
		pushdown(x);
		tot++,rt=tot;
		tree[rt]=tree[x];
		merge(rs(rt),rs(x),y);
		pushup(rt),pushup(x);
	}
	else{
		pushdown(y);
		tot++,rt=tot;
		tree[rt]=tree[y];
		merge(ls(rt),x,ls(y));
		pushup(rt),pushup(y);
	}
}
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);
	}
}
int add(char ch){
	tot++,ls(tot)=rs(tot)=0;
	tree[tot].c=ch,tree[tot].rev=0,tree[tot].siz=1,tree[tot].id=rd();
	return tot;
}
char query(int rt,int x){
    pushdown(rt);
	if(tree[ls(rt)].siz+1==x) return tree[rt].c;
	if(tree[ls(rt)].siz>=x) return query(ls(rt),x);
	else return query(rs(rt),x-tree[ls(rt)].siz-1);  
}
int main(){
	freopen("editor.in","r",stdin);
	freopen("editor.out","w",stdout);
	n=read();
	while(n--){
		char ch=getchar();
		while(ch!='I'&&ch!='D'&&ch!='C'&&ch!='R'&&ch!='Q') ch=getchar();
		if(ch=='I'){
			int x=read();
			char c=getchar();
			while(!(c>='a'&&c<='z')) c=getchar();
			int m1=0,m2=add(c);	
			split(root,root,m1,x);
			merge(root,root,m2);
			merge(root,root,m1);
		}
		if(ch=='D'){
			int l=read(),r=read();
			int m1=0,m2=0;
			split(root,root,m2,r);
			split(root,root,m1,l-1);
			merge(root,root,m2); 
		}
		if(ch=='C'){
			int l=read(),r=read(),x=read();
			int pos=0,m1=0,m2=0;
			split(root,root,m2,r);
			split(root,root,m1,l-1);
			pos=m1;
			merge(root,root,m1);
			merge(root,root,m2);
			split(root,root,m1,x);
			merge(root,root,pos);
			merge(root,root,m1);
		}
		if(ch=='R'){
			int l=read(),r=read();
			int m1=0,m2=0;
			split(root,root,m2,r);
			split(root,root,m1,l-1);
			tree[m1].rev^=1;
			merge(root,root,m1);
			merge(root,root,m2);
		}
		if(ch=='Q'){
			int x=read();
			printf("%c",query(root,x));
		}
	}
} 

此处是时间复杂度更优的代码。(所以为什么啊)

完结撒花 Q W Q !!!

你可能感兴趣的:(算法,数据结构,c++,算法,数据结构)