题意:
给定n,表示有n个集合,m次操作,操作有三种:
(1,a,b):合并集合a和集合b
(2,k):回到第k次操作的状态
(3,a,b):查询a和b是否在同一集合
数据范围:n<=1e5,m<=2e5
解法:
将数组可持久化(用可持久化线段树实现),维护并查集每个节点的父节点pre和深度dep。
合并是按秩合并。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
int lc[maxm*40],rc[maxm*40];
int pre[maxm*40],dep[maxm*40];
int rt[maxm<<1],tot;
int n,m;
void build(int l,int r,int &k){
k=++tot;
if(l==r){
pre[k]=l;
dep[k]=0;
return ;
}
int mid=(l+r)/2;
build(l,mid,lc[k]);
build(mid+1,r,rc[k]);
}
void update(int x,int val,int l,int r,int last,int &k){
k=++tot;
lc[k]=lc[last],rc[k]=rc[last];
if(l==r){
pre[k]=val;
dep[k]=dep[last];
return ;
}
int mid=(l+r)/2;
if(x<=mid)update(x,val,l,mid,lc[last],lc[k]);
else update(x,val,mid+1,r,rc[last],rc[k]);
}
int ask(int x,int l,int r,int k){//查找某版本下x的父亲
if(l==r)return pre[k];
int mid=(l+r)/2;
if(x<=mid)return ask(x,l,mid,lc[k]);
else return ask(x,mid+1,r,rc[k]);
}
void add(int x,int l,int r,int k){//将某版本下x的深度+1
if(l==r){
dep[k]++;
return ;
}
int mid=(l+r)/2;
if(x<=mid)add(x,l,mid,lc[k]);
else add(x,mid+1,r,rc[k]);
}
int ffind(int x,int k){//查询某个版本下x的父亲
int pre_x=ask(x,1,n,k);
if(x==pre_x)return x;
return ffind(pre_x,k);
}
signed main(){
scanf("%d%d",&n,&m);
build(1,n,rt[0]);
for(int i=1;i<=m;i++){
int op;scanf("%d",&op);
if(op==1){//合并a,b
int a,b;scanf("%d%d",&a,&b);
rt[i]=rt[i-1];
int x=ffind(a,rt[i]);
int y=ffind(b,rt[i]);
if(x==y)continue;
if(dep[x]>dep[y])swap(x,y);//改成dep[x]<dep[y]
update(x,y,1,n,rt[i-1],rt[i]);//把短的x接到长的y上
if(dep[x]==dep[y])add(y,1,n,rt[i]);//如果dep[x]=dep[y],dep[y]需要+1
}else if(op==2){//将版本置为rt[x]
int x;scanf("%d",&x);
rt[i]=rt[x];
}else if(op==3){//判断a,b是否在同一个集合
int a,b;scanf("%d%d",&a,&b);
rt[i]=rt[i-1];
int x=ffind(a,rt[i]);
int y=ffind(b,rt[i]);
if(x==y)puts("1");
else puts("0");
}
}
return 0;
}