并查集
并查集(Disjoint-Set)是一种可以动态维护若干不重叠集合,并支持合并与查询的数据结构。
普通并查集
处理普通合并问题
代码实现:
int par[maxn]; //存储 void init() { for(int i=1;i<=n;i++) par[i]=i; //初始化 } int get(int x) { if(x==par[x]) return x; return par[x]=get(par[x]); //路径压缩,直连树根 } void Merge(int x,int y) { pa[get(x)]=get(y); //x的树根作为y的根 }
逆向并查集
并查集一般用于构建连接,处理断开时就乏力了,为此我们可以先存下所有操作,建立一个全连接图(依题),然后倒序处理操作。
边带权并查集
当两个点之间不仅需要合并,还需要计算距离时,我们就会用到边带权的并查集。
此外,边权还可以用01表示两个关系的互斥,比如POJ1733。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<map> #define ll long long #define inf 0x3f3f3f3f using namespace std; map<int,int> mp; int cnt=1; int par[10010]; int spe[10010]; //0同性,1异性 int get(int x) { if(x==par[x]) return x; int rt=get(par[x]); spe[x]^=spe[par[x]]; return par[x]=rt; } int Merge(int x, int y, int d) { int fx=get(x); int fy=get(y); if(fx==fy){ if(spe[x]^spe[y]!=d) return 1; else return 0; } else{ par[fy]=fx; spe[fy]=(spe[x]+spe[y]+d)%2; } return 0; } int main() { for(int i=0;i<10010;i++) par[i]=i,spe[i]=0; ios::sync_with_stdio(false); int n,m,x,y,d,ans=0; string o; mp.clear(); cin>>n>>m; for(int i=1;i<=m;i++) { cin>>x>>y>>o; if(!mp[x-1]) mp[x-1]=cnt++; if(!mp[y]) mp[y]=cnt++; if(o=="even") d=0; else d=1; if(!ans&&Merge(mp[x-1],mp[y],d)) ans=i; } if(ans) cout<<ans-1<<endl; else cout<<m<<endl; return 0; }
拓展域并查集
当问题中关系不止一种时,可以拓展域(即拓展并查集par的大小)。
例如POJ1182,题目中出现了三种关系,那我们不妨再拓展出两个域,分别表示天敌(x),同类(n+x)和猎物(2*n+x)。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<queue> 5 #define ll long long 6 #define inf 0x3f3f3f3f 7 using namespace std; 8 int par[150010]; 9 int get(int x) 10 { 11 if(x==par[x]) return x; 12 return par[x]=get(par[x]); 13 } 14 void Merge(int x,int y) 15 { 16 par[get(x)]=get(y); 17 } 18 int main() 19 { 20 int n,m,ans=0; 21 int o,x,y; 22 scanf("%d%d",&n,&m); 23 for(int i=0;i<=3*n;i++) par[i]=i; 24 while(m--) 25 { 26 scanf("%d%d%d",&o,&x,&y); 27 if(x>n||y>n) 28 { 29 ans++; 30 continue; 31 } 32 if(o==1) 33 { 34 int X1=get(x),X2=get(x+2*n),Y=get(y+n); 35 if(X1==Y||X2==Y) ans++; 36 else 37 { 38 Merge(x,y); 39 Merge(x+n,y+n); 40 Merge(x+2*n,y+2*n); 41 } 42 } 43 else 44 { 45 if(x==y) 46 { 47 ans++; 48 continue; 49 } 50 else 51 { 52 int X1=get(x+n),X2=get(x),Y=get(y+n); 53 if(X1==Y||X2==Y) ans++; 54 else 55 { 56 Merge(x+n,y); 57 Merge(2*n+x,y+n); 58 Merge(x,2*n+y); 59 } 60 } 61 } 62 } 63 cout<<ans<<endl; 64 return 0; 65 }
再比如POJ2492,题目有两种关系——同性和异性,我们同样也可以用两个域表示同性(x)和异性(n+x)。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<map> #define ll long long #define inf 0x3f3f3f3f using namespace std; map<int,int> mp; int cnt=1; int par[4010]; int get(int x) { if(x==par[x]) return x; return par[x]=get(par[x]); } void Merg(int x,int y) { par[x]=y; } int main() { ios::sync_with_stdio(false); int m,t,n,x,y; cin>>t; for(int ii=1;ii<=t;ii++) { if(ii-1) cout<<endl; cout<<"Scenario #"<<ii<<':'<<endl; for(int i=0;i<4010;i++) par[i]=i; cin>>n>>m; bool f=1; while(m--) { cin>>x>>y; if(!f) continue; int x1=get(x),x2=get(x+n),y1=get(y+n),y2=get(y); if(x1==y2||y1==x2) f=0; Merg(x1,y1); Merg(x2,y2); } if(!f) cout<<"Suspicious bugs found!"<<endl; else cout<<"No suspicious bugs found!"<<endl; } return 0; }