可撤销并查集学习笔记

前言

今天在吃饭的时候听 @Kenma 讲解了一遍,发现好像很好用而且很好写,就写了。

思路

拿栈存每次操作时被合并的父亲节点,撤销时直接将其指回自己,所以不能使用路径压缩,使用按秩合并。
只能离线。

可持久化并查集

发现如果访问某个历史版本时采用一步一步撤销回去太慢了。所以我们建立一棵版本树。
形象地说:想象一条以时间为节点的链,上面连着几条跨越时间的边,当我们访问到某个时间节点时,如果他有跨越时间的边,直接访问,然后正常进行操作即可。(有没有感觉很优美而且很有意境,学魔怔了
具体而言:如果时间点 \(i\) 不是操作 2 ,那就让时间点 \(i\)\(i+1\) 连边,时间照常流转;如果时间点 \(i\) 是操作 2 ,回到 \(a_i\) 的时间点,那就让时间点 \(a_i\)\(i\) 连边(过去向未来连),因为我们需要的是 \(a_i\) 的版本。
code:

点击查看代码
#include<iostream>

using namespace std;
const int N=2e5+10;
int n,m;
int op[N],a[N],b[N],tim[N],ans[N];
struct Edge{
	int to,nxt;
}e[2*N];
int head[N],tot=1;
void add(int u,int v){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	head[u]=tot;
	return ;
}
int fa[N],siz[N],sta[N],top;
void init(){
	for(int i=1;i<=n;i++){
		fa[i]=i,siz[i]=1;
	}
	return;
}
int find(int x){
	if(x==fa[x]) return x;
	return find(fa[x]);
}
void merge(int x,int y){
	int a=find(x),b=find(y);
	if(a==b) return ;
	if(siz[a]>siz[b]) fa[b]=a,siz[a]+=siz[b],sta[++top]=b;
	else fa[a]=b,siz[b]+=siz[a],sta[++top]=a;
	return ;
}
void goback(int t){
	while(top>t){
		int y=sta[top--];
		siz[fa[y]]-=siz[y];
		fa[y]=y;
	}
	return ;
}
void dfs(int u){
	tim[u]=top;	
	if(op[u]==1) merge(a[u],b[u]);
	if(op[u]==3) ans[u]=(find(a[u])==find(b[u]))?1:0;
	for(int i=head[u];i;i=e[i].nxt){
		dfs(e[i].to);
	}
	goback(tim[u]);
}
int main(){
	cin>>n>>m;
	init();
	for(int i=1;i<=m;i++){
		cin>>op[i]>>a[i];
		if(op[i]!=2) cin>>b[i],add(i-1,i);
		else add(a[i],i);
	}
	dfs(0);
	for(int i=1;i<=m;i++){
		if(op[i]==3) cout<<ans[i]<<'\n';
	}
	return 0;
}
posted @ 2025-04-21 21:53  Tighnari  阅读(12)  评论(0)    收藏  举报