题目:BZOJ3674.
题目大意:给定
n
n
n个集合和
m
m
m个操作,要求支持:
1.格式
1
 
a
 
b
1\,a\,b
1ab,表示合并
a
,
b
a,b
a,b所在的集合.
2.格式
2
 
k
2\,k
2k,回到第
k
k
k次操作之后的状态.
3.格式
3
 
a
 
b
3\,a\,b
3ab,询问
a
,
b
a,b
a,b是否在同一个集合.
1
≤
n
,
m
≤
2
∗
1
0
5
1\leq n,m\leq 2*10^5
1≤n,m≤2∗105,强制在线.
若没有操作2,很明显这是道并查集裸题,但是操作2让这道题的并查集需要可持久化.
考虑如何给并查集可持久化,考虑并查集的过程就只是在维护一个 f a fa fa数组,我们只需要把这个 f a fa fa数组可持久化就行了.
但是路径压缩的并查集每次会修改 O ( log n ) O(\log n) O(logn)级别个位置的 f a fa fa数组,使得我们的时空复杂度都是 O ( n log 2 n ) O(n\log^2n) O(nlog2n)的,这个空间复杂度有些难以承受.
怎么办呢?考虑用启发式合并代替路径压缩,发现启发式合并的时候每次修改只会改变 O ( 1 ) O(1) O(1)级别个位置的 f a fa fa,只不过要再用可持久化数组维护一下 s i z siz siz,容易发现每次修改 s i z siz siz也只有 O ( 1 ) O(1) O(1)级别个位置,所以空间复杂度降为 O ( n log n ) O(n\log n) O(nlogn).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=200000,C=25;
int n,m;
struct tree{
int s[2],fa,siz;
}tr[N*C*2+9];
int cn,rot[N+9];
void Build(int l,int r,int &k){
k=++cn;
if (l==r){tr[k].fa=l;tr[k].siz=1;return;}
int mid=l+r>>1;
Build(l,mid,tr[k].s[0]);Build(mid+1,r,tr[k].s[1]);
}
void Add_tree_fa(int p,int x,int l,int r,int hk,int &k){
tr[k=++cn]=tr[hk];
if (l==r){tr[k].fa=x;return;}
int mid=l+r>>1;
if (p<=mid) Add_tree_fa(p,x,l,mid,tr[hk].s[0],tr[k].s[0]);
else Add_tree_fa(p,x,mid+1,r,tr[hk].s[1],tr[k].s[1]);
}
void Add_tree_siz(int p,int x,int l,int r,int hk,int &k){
tr[k=++cn]=tr[hk];
if (l==r){tr[k].siz=x;return;}
int mid=l+r>>1;
if (p<=mid) Add_tree_siz(p,x,l,mid,tr[hk].s[0],tr[k].s[0]);
else Add_tree_siz(p,x,mid+1,r,tr[hk].s[1],tr[k].s[1]);
}
int Query_fa(int p,int l,int r,int &k){
if (l==r) return tr[k].fa;
int mid=l+r>>1;
if (p<=mid) return Query_fa(p,l,mid,tr[k].s[0]);
else return Query_fa(p,mid+1,r,tr[k].s[1]);
}
int Query_siz(int p,int l,int r,int &k){
if (l==r) return tr[k].siz;
int mid=l+r>>1;
if (p<=mid) return Query_siz(p,l,mid,tr[k].s[0]);
else return Query_siz(p,mid+1,r,tr[k].s[1]);
}
int Find(int x,int id){
int fa=Query_fa(x,1,n,id);
return x^fa?Find(fa,id):x;
}
Abigail getans(){
scanf("%d%d",&n,&m);
Build(1,n,rot[0]);
int opt,x,y,fx,fy,sx,sy,last=0;
for (int i=1;i<=m;++i){
scanf("%d",&opt);
switch (opt){
case 1:
scanf("%d%d",&x,&y);
x^=last,y^=last;
fx=Find(x,rot[i-1]);fy=Find(y,rot[i-1]);
if (fx==fy){rot[i]=rot[i-1];continue;}
sx=Query_siz(fx,1,n,rot[i-1]);sy=Query_siz(fy,1,n,rot[i-1]);
if (sx<sy) swap(fx,fy);
Add_tree_fa(fy,fx,1,n,rot[i-1],rot[i]);
Add_tree_siz(fx,sx+sy,1,n,rot[i],rot[i]);
break;
case 2:
scanf("%d",&x);
x^=last;
rot[i]=rot[x];
break;
case 3:
scanf("%d%d",&x,&y);
x^=last;y^=last;
rot[i]=rot[i-1];
last=Find(x,rot[i])==Find(y,rot[i]);
printf("%d\n",last);
}
}
}
int main(){
getans();
return 0;
}
本文深入探讨了在并查集数据结构中实现可持久化的技术,通过使用启发式合并替代路径压缩,解决了操作回溯的问题。文章详细介绍了如何在不显著增加时间和空间复杂度的情况下,维护并查集的历史状态,以便于进行历史操作的查询和恢复。
577

被折叠的 条评论
为什么被折叠?



