suxxsfe

一言(ヒトコト)

可持久化数组与可持久化并查集

可持久化数组:https://www.luogu.com.cn/problem/P3919

维护这样的一个长度为 \(N\) 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值
  2. 访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)


用到主席树,每一次修改,都会创建一个新的根节点,代表了一个新的版本
主席树结构其实和线段树比较相近,叶子节点维护的是实际的数组元素,然后每向上一个层节点数减少一半
发现每次修改,都只会变动一个数组元素,那,在主席树上,就只变动它到根节点的路径上的 \(O(\log n)\) 个节点就行了

具体实现方法
现在在树上递归到区间 \([l,r]\)\(pos\) 为要修改的节点的位置,如果 \(pos\le mid\),说明它在左区间,那么新建一个节点当左区间,作为当前节点的左儿子
由于右区间完全没有变化,和之前是一样的,所以直接让它指向以前那个版本的右儿子就行了(也就是说一个节点不一定只有一个父亲,以前那个版本在这一个区间的右儿子也是现在这个版本在这个区间的右儿子)
然后就递归进入左儿子继续修改
\(pos>mid\) 则相反

这样,每一个根节点(版本),也都是一个结构完整的树
其实这里本来想放张图的,结果画的好看了就太特殊说明不了啥,稍微普遍一点就很丑很难画,所以还是自行脑补吧
查询的时候就从根节点开始一层层递归往下找就行了

关于需要的结点数,第0个版本是 \(2n\) 个点,然后每次修改会有 \(\log n\) 个点被新建,所以大概是 \(2n+m\log n\),稍大于 \(2\cdot 10^7\),保险起见直接 \(3\cdot 10^7\)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
struct tr{
	tr *ls,*rs;
	int num;
}*root[1000005],dizhi[30000005];
int tot,rootcnt;
int n;
int a[1000005]; 
void build(tr *tree,int l,int r){
	if(l==r) return tree->num=a[l],void();
	int mid=(l+r)>>1;
	tree->ls=&dizhi[++tot];tree->rs=&dizhi[++tot];
	build(tree->ls,l,mid);build(tree->rs,mid+1,r);
}
void update(tr *tree,tr *old,int l,int r,int pos,int val){
	if(l==r) return tree->num=val,void();
	int mid=(l+r)>>1;
	if(pos<=mid)
		tree->ls=&dizhi[++tot],tree->rs=old->rs,update(tree->ls,old->ls,l,mid,pos,val);
	else
		tree->rs=&dizhi[++tot],tree->ls=old->ls,update(tree->rs,old->rs,mid+1,r,pos,val);
}
inline void Update(int v,int pos,int val){
	root[++rootcnt]=&dizhi[++tot];
	update(root[rootcnt],root[v],1,n,pos,val);
}
int get(tr *tree,int l,int r,int pos){
	if(l==r) return tree->num;
	int mid=(l+r)>>1;
	return pos<=mid?get(tree->ls,l,mid,pos):get(tree->rs,mid+1,r,pos);
}
inline int Get(int v,int pos){
	root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[v];
	return get(root[v],1,n,pos);
}
int main(){
	n=read();int m=read();
	for(reg int i=1;i<=n;i++) a[i]=read();
	build(root[0]=&dizhi[0],1,n);
	reg int v,op,loc;
	while(m--){
		v=read();op=read();loc=read();
		if(op==1) Update(v,loc,read());
		else printf("%d\n",Get(v,loc));
	}
	return 0;
}

 

可持久化并查集:https://www.luogu.com.cn/problem/P3402
至于为什么这两个放在一起说,其实可持久化并查集不就是把原来并查集的数组换成主席树吗。。。

但是这里不能用路径压缩,而是按大小合并
因为用路径压缩时,说的是均摊 \(O(\log n)\),也就是每一次操作并不是全都是 \(O(\log n)\) 的,可能有某一次操作的复杂度远大于此,那么一加上可持久化,如果不断执行这一次操作再回到执行前,复杂度就错误了

此时每一次合并集合,都需要修改大小小的那个点的 \(fa\) 和大小大的那个点的 \(size\),更改两个值,所以每次修改生成两个版本
如果强行和为一个,内存上节省不多(最差情况只省了一个根节点),但后一个修改抹掉前一个修改的值的情况比较难处理,所以意义不大
那么用 \(num_i\) 记录第 \(i\) 个版本在第几个根上

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
struct data{
	int x,size;
};
struct tr{
	tr *ls,*rs;
	data a;
}dizhi[8000005],*root[400005];
int tot,rootcnt;
int n;
int num[200005];
void build(tr *tree,int l,int r){
	if(l==r) return tree->a=(data){l,1},void();
	int mid=(l+r)>>1;
	tree->ls=&dizhi[++tot];tree->rs=&dizhi[++tot];
	build(tree->ls,l,mid);build(tree->rs,mid+1,r);
}
data get(tr *tree,int l,int r,int pos){
	if(l==r) return tree->a;
	int mid=(l+r)>>1;
	return pos<=mid?get(tree->ls,l,mid,pos):get(tree->rs,mid+1,r,pos);
}
data find(int k){
	data fa=get(root[rootcnt],1,n,k);
	if(fa.x==k) return fa;
	return find(fa.x);
}
void update(tr *tree,tr *old,int l,int r,int pos,data now){
	if(l==r) return tree->a=now,void();
	int mid=(l+r)>>1;
	if(pos<=mid){
		tree->ls=&dizhi[++tot];tree->rs=old->rs;
		update(tree->ls,old->ls,l,mid,pos,now);
	}
	else{
		tree->rs=&dizhi[++tot];tree->ls=old->ls;
		update(tree->rs,old->rs,mid+1,r,pos,now);
	}
}
inline void link(int a,int b){
	data x=find(a),y=find(b);
	root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[rootcnt-1];
	if(x.x==y.x) return;
	if(x.size>y.size){
		update(root[rootcnt],root[rootcnt-1],1,n,y.x,(data){x.x,y.size});
		root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[rootcnt-1];
		update(root[rootcnt],root[rootcnt-1],1,n,x.x,(data){x.x,x.size+y.size});
	}
	else{
		update(root[rootcnt],root[rootcnt-1],1,n,x.x,(data){y.x,x.size});
		root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[rootcnt-1];
		update(root[rootcnt],root[rootcnt-1],1,n,y.x,(data){y.x,x.size+y.size});
	}
}
int main(){
	n=read();int m=read();
	reg int op,a,b;
	root[0]=&dizhi[0];
	build(root[0],1,n);
	for(reg int i=1;i<=m;i++){
		op=read();a=read();
		if(op==1){
			b=read();link(a,b);
		}
		else if(op==2){
			root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[num[a]];
		}
		else{
			b=read();
			printf("%d\n",find(a).x==find(b).x);
			root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[rootcnt-1];
		}
		num[i]=rootcnt;
	}
	return 0;
}
posted @ 2020-07-20 22:17  suxxsfe  阅读(128)  评论(0编辑  收藏  举报