并查集
作用:
并查集顾名思义就是有“合并集合”和“查找集合中的元素”两种操作的关于数据结构。
代表元思想:
用集合中的某个元素来代表这个集合,该元素称为集合的代表元
。
组织结构:
一个集合内的所有元素组织成以代表元为根的树形结构。也就是是说同一个集合中的元素在同一棵树里。
合并两个集合:
将一棵树作为另一棵树的子树。也就是将代表元作为别人的子节点。
查找集合中的两个元素:
讨论他们代表元是否一样。
一些题目:
POJ1703 Find them, Catch them
题意:
有两个帮派,
操作D:表示 a,b不属于一个帮派 。操作A:询问a,b是否为同一个帮派。
思路:
同一个代表元中的东西表示他们属于同一集合。我们可以很容易表示a,b属于同一个集合,那么如何表示属于不同的集合。
我们可以用a+N表示a的对立面,如果a+N和b在同一集合内,那么表示a,b不在同一个集合内。
代码:
1 #include<cstdio> 2 #include<cstring> 3 const int MAXN=1e5+7; 4 int par[MAXN*2]; 5 int find(int x){ 6 return par[x]==x?par[x]:par[x]=find(par[x]); 7 } 8 void unit(int a,int b){ 9 a=find(a); 10 b=find(b); 11 if(a!=b){ 12 par[a]=b; 13 } 14 } 15 int main(){ 16 int T;scanf("%d",&T); 17 while(T--){ 18 int N,M;scanf("%d%d",&N,&M);getchar(); 19 for(int i=1;i<=N*2;++i){ 20 par[i]=i; 21 } 22 for(int i=0;i<M;++i){ 23 char ch=getchar();int a,b; 24 scanf("%d%d",&a,&b);getchar(); 25 if(ch=='D'){ 26 unit(a,b+N); 27 unit(a+N,b); 28 }else{ 29 int x1=find(a),x2=find(a+N); 30 int y1=find(b),y2=find(b+N); 31 if(x1==y1||x2==y2)printf("In the same gang.\n"); 32 else if(x1==y2||x2==y1)printf("In different gangs.\n"); 33 else printf("Not sure yet.\n"); 34 } 35 } 36 } 37 return 0; 38 }
也可以用带权并查集做:
这里有篇讲得不错的博客:http://blog.csdn.net/mengxiang000000/article/details/51142345
大概思想就是通过这样的思想:
敌人的敌人是朋友。
通过计算两个节点间的距离来确定这两个点是不是同一个集合里的。
代码:
#include<cstdio> #include<cstring> const int MAXN=1e5+7; int par[MAXN],sum[MAXN]; int find(int x){ if(par[x]==x)return x; int top=find(par[x]); sum[x]+=sum[par[x]]; return par[x]=top; } int main(){ int T;scanf("%d",&T); while(T--){ int N,Q;scanf("%d%d",&N,&Q);getchar(); for(int i=1;i<=N;++i)par[i]=i,sum[i]=0; for(int i=0;i<Q;++i){ char ch;int x,y; scanf("%c%d%d",&ch,&x,&y);getchar(); if(ch=='D'){ int xx=find(x); int yy=find(y); if(xx==yy)continue; par[xx]=yy; //printf("par[%d]=%d\n",xx,par[xx]); sum[xx]=sum[y]+sum[x]+1; //printf("sum[%d]=%d\n",xx,sum[xx]); }else{ int xx=find(x); int yy=find(y); if(yy==xx){ if((sum[x]-sum[y])%2==0){ printf("In the same gang.\n"); }else{ printf("In different gangs.\n"); } }else{ if(N==2)printf("In different gangs.\n"); else printf("Not sure yet.\n"); } } } } return 0; }
ural 1003 parity
题意:
给你一些信息l,r,odd/even 代表从l到r的和为奇数或者偶数。问从哪一条开始错了。
思路:
考虑到数据范围很大,但N不大,可以考虑离散化。然后sum[l...r]=sum[r]-sum[l-1],也就是转化为前缀和。如果是偶数那么r和l-1在同一个集合里,否则就不是。我们认为在一个集合里的奇偶性是一样的。需要注意的是需要先判断是否符合条件,再进行集合的合并。顺序千万不要弄错!因为先进行合并会改变他们的关系。
另外,对在不在一个集合里,奇数偶数这种转化为并查集思想一定要敏感。
代码:
#include<cstdio> #include<cstring> #include<map> using namespace std; map<int,int>m; const int MAXN=1e4+7; int par[MAXN*2]; int tot; int get(int x){ if(!m[x])m[x]=tot++; return m[x]; } int find(int x){ return par[x]==x?par[x]:par[x]=find(par[x]); } void unit(int x,int y){ //printf("unit:x=%d,y=%d\n",x,y); x=find(x); y=find(y); if(x!=y){ par[x]=y; } } int main(){ int T; while(scanf("%d",&T)&&T!=-1){ int N;scanf("%d",&N); for(int i=1;i<=N*2*2;++i)par[i]=i; m.clear(); tot=1; int ans=N; for(int k=1;k<=N;++k){ char s[10]; int x,y;scanf("%d%d%s",&x,&y,s); if(ans!=N)continue; x--; x=get(x);y=get(y); if(s[0]=='e'){ int xx=find(x),yy=find(y); if(find(x)==find(y+2*N)||find(x+2*N)==find(y)){ ans=k-1; }else if(xx!=yy){ unit(x,y); unit(x+2*N,y+2*N); } }else{ int xx=find(x),yy=find(y); if(find(x)==find(y)||find(x+2*N)==find(y+2*N)){ ans=k-1; }else if(xx!=yy){ unit(x,y+2*N); unit(x+2*N,y); } } } printf("%d\n",ans); } return 0; }