给定初始n个集合,开始每个集合中只有i一个数,
之后给定m次操作
1 a b代表合并ab所在的集合
2 k 代表回到第二次操作
3 a b 代表查询a b是否数属于同一集合 是输出1,否输出0
很明显操作2说明了我们需要维护的是一个可持久化并查集,因为他需要支持历史版本的回溯。而既然是并查集,我们知道普通并查集的合并查询操作的核心都是fa数组,也就是父节点,因此可持久化并查集一个需要维护的就是我们的fa数组可持久化
而并查集中有两种我们常见我的优化方式一是路径压缩,二是按秩合并(就是按数高小的合并到大的上),整体上减小树的高度,从而达到优化效果。但是第一种优化路径压缩在可持久化结构中,会产生巨大的空间消耗(好像是),因此我们只能采用第二种路径压缩的方式来优化。这样我们就需要再来维护另一个可持久化的数组dep数组,代表并查集的高
剩下的具体实现看代码吧
借鉴大佬的代码 还有讲解视频挺清晰的
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2e5+7;
//可持久化 并查集 经典模板题
//1 a b 合并ab所在的集合 并查集的操作
//2 k 回到第k次操作由此可以想到 用一个 可持久化数组 维护一下节点
//3 a b 询问ab是否在同一个区间 并查集的询问操作
//用主席数维护一个fa和一个dep数组
//merge有两种写法 一种是 merge里面带判断 另一种先判断秩的关系 在merge
int dep[MAXN],fa[MAXN],n,size,m;//dep和fa这两个数组时用来可持久化维护的 相当于记录不同功能的不同版本
struct HJT_tree
{
int l,r,sum;//这个sum 一部分是记录fa数组的祖先节点的值 另一部分是记录dep数组的值 用到哪个时候调用相应的内存池即可
}tree[MAXN*40*2];
//可持久化数组操作
void build(int &now,int l,int r)//需要对now进行修改的时候 再传引用即(&now)
{
now = ++size;
if(l == r){
tree[now].sum = l;//dep初始化都是0
return ;
}
int mid = (l+r)>>1;
build(tree[now].l,l,mid);
build(tree[now].r,mid+1,r);
}
void update(int l,int r,int pre,int &now,int pos,int val)
{
now = ++size;
tree[now] = tree[pre];
if(l == r){
tree[now].sum = val;
return ;
}
int mid = (l+r)>>1;
if(pos <= mid) update(l,mid,tree[pre].l,tree[now].l,pos,val);
else update(mid+1,r,tree[pre].r,tree[now].r,pos,val);
}
int query(int now,int l,int r,int pos)
{
if(l == r) return tree[now].sum;
int mid = (l+r)>>1;
if(pos <= mid) return query(tree[now].l,l,mid,pos);
else return query(tree[now].r,mid+1,r,pos);
}
//以上是树的操作
//并查集的相关操作
int Find(int now,int x)
{
int fx = query(fa[now],1,n,x);
return fx == x ? x : Find(now,fx);//注意这里不要路径压缩
}
void merge(int now,int x,int y)
{
int fx = Find(now-1,x),fy = Find(now-1,y);//这里是前一个版本的操作 因为新的版本还没生成 (也可以在主函数里传前一个版的参数)
if(fx == fy){
fa[now] = fa[now-1];
dep[now] = dep[now-1];
return ;
}
else{//注意后面的操作都是对fx和fy操作 合并修改的时候都是既要改深度也要改高度
int depx = query(dep[now-1],1,n,fx);
int depy = query(dep[now-1],1,n,fy);
if(depx != depy){//这里 高度不一样的话 把小的连接到大的上面来完成
if(depx > depy) swap(fx,fy);
update(1,n,fa[now-1],fa[now],fx,fy);
dep[now] = dep[now-1];
}
if(depx == depy){
update(1,n,fa[now-1],fa[now],fx,fy);
update(1,n,dep[now-1],dep[now],fy,depy+1);
}
}
}
//以上是并查集的相关操作
int main()
{
scanf("%d%d",&n,&m);
build(fa[0],1,n);//这类dep初始化都看成0即可 所以不需要 我们构建
for(int v = 1;v <= m;v ++){
int op,a,b,k;
scanf("%d",&op);
switch(op){
case 1:
scanf("%d%d",&a,&b);
merge(v,a,b);
break;
case 2:
scanf("%d",&k);
fa[v] = fa[k];//回溯状态
dep[v] = dep[k];
break;
case 3:
scanf("%d%d",&a,&b);
fa[v] = fa[v-1];//产生新状态
dep[v] = dep[v-1];
int fa = Find(v,a),fb = Find(v,b);
printf(fa == fb?"1\n":"0\n");
break;
}
}
return 0;
}