可持久化并查集学习笔记

可持久化并查集

模板

Luogu P3402 可持久化并查集'
可持久化并查集支持三个操作:

  • 回到某个历史版本(可持久化)。
  • 合并两个集合(并查集)。
  • 查询两个元素是否在同一个集合中(并查集)。

思路

很明显,对于第一个操作,就用主席树实现,也就是把当前版本的根节点设成要求版本的根节点就行了。
至于第二个合并的操作,因为如果用路径压缩的优化方式,会因为时间复杂度是均摊的而导致 TLE,所以必须做到每次严格只访问 \(O(\log_2n)\) 个节点的方法,也就是按秩合并(按集合深度合并)或启发式合并也行(按集合元素个数合并)。
至于证明,在蒟蒻的并查集学习笔记中,有需要可以点链接查看。

解释

先把代码里用的宏定义和快读快写放一下:

#include <bits/stdc++.h>
#define lc s[p].l,l,m
#define rc s[p].r,m+1,r
#define MID int m=(l+r)>>1;
using namespace std;int rd(){
	int w=0,v=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')v=-1;c=getchar();}
	while(c>='0'&&c<='9'){w=(w<<1)+(w<<3)+(c&15);c=getchar();}return w*v;
}void wr(int x){
    char c[20];int l=0;if(x<0){putchar((1<<5)+(1<<3)+(1<<2)+1);x=~x+1;}
    do{c[l++]=x%10+(1<<4)+(1<<5);x/=10;}while(x>0);for(int i=l-1;i>=0;i--)putchar(c[i]);
}const int N=3e5+100;

首先是建一开始的空树,和主席树的板子一样,只是此题中维护的是每个元素的父亲。

void build(int &p,int l,int r){p=++cnt;if(l==r){s[p].fa=l;return ;}MID build(lc);build(rc);}
//最一开始的父亲就是自己,所以 s[p].fa=l;

至于集合合并的操作,就套用可持久化数组中的将一个点合并到另一个点上的操作。

void merge(int &p,int l,int r,int last,int x,int f){
	p=++cnt;s[p].l=s[last].l;s[p].r=s[last].r;if(l==r){s[p].fa=f;s[p].dep=s[last].dep;return ;}
//继承之前版本的值
	MID if(x<=m)merge(lc,s[last].l,x,f);else merge(rc,s[last].r,x,f);
}

还有修改节点深度的操作。
因为当两个集合的深度相同时,要改变其中一个的深度。

void update(int p,int l,int r,int x){if(l==r){s[p].dep++;return ;}MID if(x<=m)update(lc,x);else update(rc,x);}
//直接递归,到达叶子节点时修改即可。

因为要查找一个节点的祖先,所以有一个查找某个值在数组中下标的函数。

int query(int p,int l,int r,int x){if(l==r)return p;MID if(x<=m)return query(lc,x);else return query(rc,x);}

最后就是暴力查找祖先的函数。

int cz(int p,int x){int now=query(p,1,n,x);return s[now].fa==x?now:cz(p,s[now].fa);}

以上就是用于实现可持久化并查集的函数。
所有函数的时间复杂度都是 \(O(\log_2n)\) 的,但空间需要 \(O(n\log_2n)\)
下面是主函数中的操作。
并查集按秩合并:

y=rd();rt[i]=rt[i-1];fx=cz(rt[i],x),fy=cz(rt[i],y);
if(fx!=fy){
    if(s[fx].dep>s[fy].dep)swap(fx,fy);
    merge(rt[i],1,n,rt[i-1],s[fx].fa,s[fy].fa);
    if(s[fx].dep==s[fy].dep)update(rt[i],1,n,s[fy].fa);
}//如果深度相同,则改变其中之一的深度

查询:

y=rd();rt[i]=rt[i-1];fx=cz(rt[i],x),fy=cz(rt[i],y);
if(s[fx].fa==s[fy].fa)puts("1");else puts("0");

完整代码

#include <bits/stdc++.h>
#define lc s[p].l,l,m
#define rc s[p].r,m+1,r
#define MID int m=(l+r)>>1;
using namespace std;int rd(){
	int w=0,v=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')v=-1;c=getchar();}
	while(c>='0'&&c<='9'){w=(w<<1)+(w<<3)+(c&15);c=getchar();}return w*v;
}void wr(int x){
    char c[20];int l=0;if(x<0){putchar((1<<5)+(1<<3)+(1<<2)+1);x=~x+1;}
    do{c[l++]=x%10+(1<<4)+(1<<5);x/=10;}while(x>0);for(int i=l-1;i>=0;i--)putchar(c[i]);
}const int N=3e5+100;int n,q,rt[N*30],cnt,o,x,y,fx,fy;struct tree{int l,r,dep,fa;}s[N*30];
void build(int &p,int l,int r){p=++cnt;if(l==r){s[p].fa=l;return ;}MID build(lc);build(rc);}
void merge(int &p,int l,int r,int last,int x,int f){
	p=++cnt;s[p].l=s[last].l;s[p].r=s[last].r;if(l==r){s[p].fa=f;s[p].dep=s[last].dep;return ;}
	MID if(x<=m)merge(lc,s[last].l,x,f);else merge(rc,s[last].r,x,f);
}void update(int p,int l,int r,int x){if(l==r){s[p].dep++;return ;}MID if(x<=m)update(lc,x);else update(rc,x);}
int query(int p,int l,int r,int x){if(l==r)return p;MID if(x<=m)return query(lc,x);else return query(rc,x);}
int cz(int p,int x){int now=query(p,1,n,x);return s[now].fa==x?now:cz(p,s[now].fa);}
int main(){
	n=rd(),q=rd();build(rt[0],1,n);
	for(int i=1;i<=q;i++){
		o=rd(),x=rd();
		if(o==1){
			y=rd();rt[i]=rt[i-1];fx=cz(rt[i],x),fy=cz(rt[i],y);
			if(fx!=fy){
				if(s[fx].dep>s[fy].dep)swap(fx,fy);
				merge(rt[i],1,n,rt[i-1],s[fx].fa,s[fy].fa);
				if(s[fx].dep==s[fy].dep)update(rt[i],1,n,s[fy].fa);
			}
		}else if(o==2)rt[i]=rt[x];
		else{y=rd();rt[i]=rt[i-1];fx=cz(rt[i],x),fy=cz(rt[i],y);if(s[fx].fa==s[fy].fa)puts("1");else puts("0");}
	}return 0;
}
posted @ 2022-04-07 16:42  AIskeleton  阅读(38)  评论(0编辑  收藏  举报