n个集合 m个操作
操作:
-
1 a b
合并a,b所在集合 -
2 k
回到第k次操作之后的状态(查询算作操作) -
3 a b
询问a,b是否属于同一集合,是则输出1否则输出0
输入样例#1:
5 6
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2
输出样例#1:
1
0
1
说明
[1≤n≤10^5,1≤m≤2*10^5
]
By zky 出题人大神犇
Solution
最近几天我看到2018NOI day1 T1 可以用可持化并查集做,我最喜欢可持久化数据结构了,于是就去学了一波。
可持久化并查集利用的是可持久化数组+按秩合并并查集(好像不可以路路径压缩),可持久化数组不会的话可以看我的另一篇博客可持久化数组。我们用数组fa[]来存一个节点的父亲,而不是祖先。dep[]数组存的是当前并查集的深度,因为要按秩合并。剩下的在代码中解释,不要在意我的中文式打法。
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int root[201000],fa[10001000],tot,dep[10000010],n;
struct TREE
{
int ln,rn;
}t[10010000];//主席树
void build(int &node,int l,int r)
{
node=++tot;
if(l==r) {fa[node]=l;dep[node]=1;return;}
int mid=(l+r)/2;
build(t[node].ln,l,mid);
build(t[node].rn,mid+1,r);
}
int find(int node,int l,int r,int x)
{
if(l==r) return node;
int mid=(l+r)/2;
if(x<=mid) return find(t[node].ln,l,mid,x);
else return find(t[node].rn,mid+1,r,x);
}
int getfa(int node,int x)
{
int p=find(node,1,n,x);
if(fa[p]==x) return p;
else return getfa(node,fa[p]);
}
void gai(int &node,int last,int l,int r,int x,int y)
{
node=++tot;
if(l==r) {fa[node]=y;dep[node]=dep[last];return;}
int mid=(l+r)/2;
t[node]=t[last];
if(x<=mid) gai(t[node].ln,t[last].ln,l,mid,x,y);
else gai(t[node].rn,t[last].rn,mid+1,r,x,y);
}
void xiu(int node,int l,int r,int x)
{
if(l==r) {dep[node]++;return;}
int mid=(l+r)/2;
if(x<=mid) xiu(t[node].ln,l,mid,x);
else xiu(t[node].rn,mid+1,r,x);
}
void merge(int x,int y,int i)//合并
{
if(dep[x]>dep[y]) swap(x,y);//按秩合并
gai(root[i],root[i-1],1,n,fa[x],fa[y]);
if(dep[x]==dep[y]) xiu(root[i],1,n,fa[y]);//深度一样,修改父节点深度
}
int main()
{
int m,opt,x,y;
cin>>n>>m;
build(root[0],1,n);
for(int i=1;i<=m;i++)
{
scanf("%d",&opt);
root[i]=root[i-1];
if(opt==1)
{
scanf("%d%d",&x,&y);
x=getfa(root[i],x);y=getfa(root[i],y);
if(fa[x]!=fa[y]) merge(x,y,i);
}
if(opt==2)
scanf("%d",&x),root[i]=root[x];
if(opt==3)
{
scanf("%d%d",&x,&y);
x=getfa(root[i],x);y=getfa(root[i],y);
if(fa[x]!=fa[y]) printf("0
");
else printf("1
");
}
}
}
学完之后就差不多可以打2018NOI归程了。