【模板】可持久化并查集

luogu传送门
题目描述
给定 n 个集合,第 i个集合内初始状态下只有一个数,为 i。

有 m 次操作。操作分为 3 种:
1 a b 合并 a,b 所在集合;

2 k 回到第 k 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态;

3 a b 询问 a,b 是否属于同一集合,如果是则输出 1 ,否则输出 0。

输入格式
第一行两个整数,n,m。

接下来 m 行,每行先输入一个数 opt。若 opt=2则再输入一个整数 k,否则再输入两个整数 a,b,描述一次操作。

输出格式
对每个操作 3,输出一行一个整数表示答案。
话说没做之前一直以为又是什么新算法,做了发现其实还是主席树的运用吧,
首先,我们要会主席树和并查集;
并查集中有几种合并方式:

一种是直接暴力连父亲显然不可能考你这个

一种是路径压缩的合并(这个在普通并查集中很常用,但是好像无法在可持久化并查集中用,蒟蒻表示不知道为啥);

还有一种是按秩合并,也就是可持久化并查集中常用的合并方式!其实也就是一种类似于启发式合并的方式,每一次合并时选择一个深度小的点向深度大的合并。这样就可以保证并查集的高度不会增长的太快,保证高度尽量均衡。

可持久化并查集其实就是指的用可持久化数组维护并查集中的Fa与按秩合并所需要的dep
所谓可持久化并查集,可以进行的操作就只有几个:
回到历史版本
合并两个集合
查询节点所在集合的祖先,
当然,因此也可以判断是否在同一个集合中!对于1操作,我们可以很轻松的利用主席树实现。就直接把当前版本的根节点定为第k个版本的根节点就行了!2操作便是记住按秩合并。3操作直接按并查集那样查就是了。
直接上代码吧!

#include<bits/stdc++.h>
using namespace std;
const int N=3e6;
int n,m,fa[N],L[N],R[N],dep[N],cnt,rt[N],saber,x,y;
int Read()
{
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch))	{if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return f*x;
}
int build(int l,int r)
{
	cnt++;
	int u=cnt;
	if(l==r){fa[u]=l;return u;}
	int mid=(l+r)>>1;
	L[u]=build(l,mid),R[u]=build(mid+1,r);
	return u;
}
int query(int Rt,int l,int r,int x)
{
	if(l==r) return Rt;
	int mid=(l+r)>>1;
	if(x<=mid) return query(L[Rt],l,mid,x);
	else return query(R[Rt],mid+1,r,x);
}
int Get(int Rt,int x)
{
	int pos=query(Rt,1,n,x);
	if(fa[pos]==x) return pos;
	return Get(Rt,fa[pos]);
}
int merge(int l,int r,int pre,int f1,int f2)
{
	cnt++;
	int u=cnt;
	L[u]=L[pre],R[u]=R[pre];
	if(l==r)
	{
		dep[u]=dep[pre];
		fa[u]=f2;
		return u;
	}
	int mid=(l+r)>>1;
	if(f1<=mid) L[u]=merge(l,mid,L[pre],f1,f2);
	else R[u]=merge(mid+1,r,R[pre],f1,f2);
	return u;
}
void updatdep(int Rt,int l,int r,int f)
{
	if(l==r){dep[Rt]++;return;}
	int mid=(l+r)>>1;
	if(f<=mid) updatdep(L[Rt],l,mid,f);
	else updatdep(R[Rt],mid+1,r,f);
}
int main()
{
	n=Read(),m=Read();
	rt[0]=build(1,n);
	for(int i=1;i<=m;i++)
	{
		saber=Read();
		if(saber==2) x=Read(),rt[i]=rt[x];
		if(saber==1)
		{
			x=Read(),y=Read();
			rt[i]=rt[i-1];
			int f1=Get(rt[i-1],x),f2=Get(rt[i-1],y);//这里返回的并不是它真正祖先,只是为了减少Get函数调用。
			if(f1!=f2)
			{
				if(dep[f1]>dep[f2]) swap(f1,f2);
				rt[i]=merge(1,n,rt[i-1],fa[f1],fa[f2]);
				if(dep[f1]==dep[f2]) updatdep(rt[i],1,n,fa[f2]);
			}
		}
		if(saber==3)
		{
			x=Read(),y=Read();
			rt[i]=rt[i-1];
			int f1=fa[Get(rt[i-1],x)],f2=fa[Get(rt[i-1],y)];
			if(f1==f2) printf("1\n");
			else printf("0\n");			
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值