可持久化并查集的两种写法
第一种(bzoj3673):
Description
n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
0<n,m<=2*10^4
Input
Output
Sample Input
5 6
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2
Sample Output
1
0
1
这道题可以保存操作,因为每次合并,改变的只有一个点的父亲,所以我们可以记录每次合并的两个点,当我们要回到历史版本时,只需令当前的等于历史版本的那个点即可。
0
1
这道题可以保存操作,因为每次合并,改变的只有一个点的父亲,所以我们可以记录每次合并的两个点,当我们要回到历史版本时,只需令当前的等于历史版本的那个点即可。
具体实现我们可以建立一个类似链表的数据结构,保存t时刻的操作,即u和它的父亲v,last指向这次操作的上一次操作,如果是回到历史版本只需last指向历史值,即可。当我们每次找x的父亲节点时,就沿着last往回找,直到结束。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cmath> 4 #include<cstring> 5 #include<cstdio> 6 #include<algorithm> 7 #include<string> 8 #include<map> 9 #include<queue> 10 #include<vector> 11 #include<set> 12 #define inf 1000000000 13 #define maxn 20000+5 14 #define maxm 10000+5 15 #define eps 1e-10 16 #define ll long long 17 #define for0(i,n) for(int i=0;i<=(n);i++) 18 #define for1(i,n) for(int i=1;i<=(n);i++) 19 #define for2(i,x,y) for(int i=(x);i<=(y);i++) 20 #define for3(i,x,y) for(int i=(x);i>=(y);i--) 21 #define for4(i,x) for(int i=head[x],y=e[i].go;i;i=e[i].next,y=e[i].go) 22 using namespace std; 23 int read(){ 24 int x=0,f=1;char ch=getchar(); 25 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 26 while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();} 27 return x*f; 28 } 29 int n,m,cnt; 30 struct data{ 31 int last,x,fx; 32 }fa[maxn]; 33 int find(int now,int x){ 34 if(!now)return x; 35 if(x!=fa[now].x)return find(fa[now].last,x); 36 return fa[now].fx; 37 } 38 int getfa(int x){ 39 if(x==find(cnt,x))return x; 40 return getfa(find(cnt,x)); 41 } 42 void merge(int x,int y){ 43 int p=getfa(x),q=getfa(y); 44 if(p!=q)fa[cnt]=(data){cnt-1,q,p}; 45 } 46 int query(int x,int y){ 47 return getfa(x)==getfa(y)?1:0; 48 } 49 int main(){ 50 //freopen("input.txt","r",stdin); 51 //freopen("output.txt","w",stdout); 52 n=read();m=read(); 53 for1(i,m){ 54 int x=read(); 55 cnt++; 56 if(x==1){ 57 int u=read(),v=read(); 58 fa[cnt]=fa[cnt-1]; 59 merge(u,v); 60 } 61 else if(x==2){ 62 int u=read(); 63 fa[cnt]=fa[u]; 64 } 65 else{ 66 int u=read(),v=read(); 67 fa[cnt]=fa[cnt-1]; 68 printf("%d\n",query(u,v)); 69 } 70 } 71 return 0; 72 }
第二种(bzoj3674):
Description
Description:
自从zkysb出了可持久化并查集后……
hzwer:乱写能AC,暴力踩标程
KuribohG:我不路径压缩就过了!
ndsf:暴力就可以轻松虐!
zky:……
n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
请注意本题采用强制在线,所给的a,b,k均经过加密,加密方法为x = x xor lastans,lastans的初始值为0
0<n,m<=2*10^5
Input
Output
Sample Input
5 6
1 1 2
3 1 2
2 1
3 0 3
2 1
3 1 2
1 1 2
3 1 2
2 1
3 0 3
2 1
3 1 2
Sample Output
1
0
1
0
1
用可持久化线段树维护数组,叶子节点就是fa数组。
对于可持久化线段树,我们实际上是把每次修改点变成新增点,我们可以想到,每次修改,修改的是线段树上从根到叶子节点上的一条链,也就是每次修改最多增加logn个节点,所以空间复杂度并不会变得很差,同时我们会发现,实际上我们很多节点都是重复的,我们可以把新的点直接指向这些没用变动的点。
对于并查集的可持久化操作,实际上就是把原先直接访问数组的操作,变成了在线段树上单点查询,修改变成单点修改。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cmath> 4 #include<cstring> 5 #include<cstdio> 6 #include<algorithm> 7 #include<string> 8 #include<map> 9 #include<queue> 10 #include<vector> 11 #include<set> 12 #define inf 1000000000 13 #define maxn 200000+5 14 #define maxm 10000000+5 15 #define eps 1e-10 16 #define ll long long 17 #define for0(i,n) for(int i=0;i<=(n);i++) 18 #define for1(i,n) for(int i=1;i<=(n);i++) 19 #define for2(i,x,y) for(int i=(x);i<=(y);i++) 20 #define for3(i,x,y) for(int i=(x);i>=(y);i--) 21 #define for4(i,x) for(int i=head[x],y=e[i].go;i;i=e[i].next,y=e[i].go) 22 using namespace std; 23 int read(){ 24 int x=0,f=1;char ch=getchar(); 25 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 26 while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();} 27 return x*f; 28 } 29 int n,m,sz,last; 30 int root[maxn],ls[maxm],rs[maxm],v[maxm],deep[maxm]; 31 void build(int &k,int l,int r){ 32 if(!k)k=++sz; 33 if(l==r){v[k]=l;return ;} 34 int mid=(l+r)>>1; 35 build(ls[k],l,mid); 36 build(rs[k],mid+1,r); 37 } 38 void modify(int l,int r,int x,int &y,int pos,int val){ 39 y=++sz; 40 if(l==r){v[y]=val;deep[y]=deep[x];return ;} 41 ls[y]=ls[x];rs[y]=rs[x]; 42 int mid=(l+r)>>1; 43 if(pos<=mid) 44 modify(l,mid,ls[x],ls[y],pos,val); 45 else modify(mid+1,r,rs[x],rs[y],pos,val); 46 } 47 int query(int k,int l,int r,int pos){ 48 if(l==r)return k; 49 int mid=(l+r)>>1; 50 if(pos<=mid)return query(ls[k],l,mid,pos); 51 else return query(rs[k],mid+1,r,pos); 52 } 53 void add(int k,int l,int r,int pos){ 54 if(l==r){deep[k]++;return ;} 55 int mid=(l+r)>>1; 56 if(pos<=mid)add(ls[k],l,mid,pos); 57 else add(rs[k],mid+1,r,pos); 58 } 59 int find(int k,int x){ 60 int p=query(k,1,n,x); 61 if(x==v[p])return p; 62 return find(k,v[p]); 63 } 64 int main(){ 65 //freopen("input.txt","r",stdin); 66 //freopen("output.txt","w",stdout); 67 n=read(),m=read(); 68 build(root[0],1,n); 69 int f,k,a,b; 70 for1(i,m){ 71 f=read(); 72 if(f==1){ 73 root[i]=root[i-1]; 74 a=read();b=read();a=a^last;b=b^last; 75 int p=find(root[i],a),q=find(root[i],b); 76 if(v[p]==v[q])continue; 77 if(deep[p]>deep[q])swap(q,p); 78 modify(1,n,root[i-1],root[i],v[p],v[q]); 79 if(deep[p]==deep[q])add(root[i],1,n,v[q]); 80 } 81 else if(f==2){ 82 k=read();k=k^last;root[i]=root[k]; 83 } 84 else{ 85 root[i]=root[i-1]; 86 a=read();b=read();a=a^last;b=b^last; 87 int p=find(root[i],a),q=find(root[i],b); 88 if(v[p]==v[q])last=1; 89 else last=0; 90 printf("%d\n",last); 91 } 92 } 93 return 0; 94 }