并查集
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
这样说可能不是很易懂,来看一道例题:题目链接 poj2524
当今世界上有许多不同的宗教,要了解它们是很困难的。你想知道你们学校的学生信仰多少种不同的宗教。你知道你们学校有n个学生(0 <n(= 50000))。你不可能问每个学生的宗教信仰。此外,许多学生对表达他们的信仰感到不自在。避免这些问题的一种方法是问m (0 <=m (= n(n-1)/2)对学生,问他们是否信仰相同的宗教(例如,他们可能知道他们是否参加同一个教堂)。从这些数据中,你可能不知道每个人都信仰什么,但你可以了解校园里可能代表多少种不同宗教的上限。你可能认为每个学生最多只信奉一种宗教。
输入输入由许多情况组成。下面的m行分别由两个整数i和j组成,指定学生i和j信仰同一宗教。学生们编号为1至n.输入端由n=m=0的行指定。
输出对于每个测试用例,在一行中打印案例编号(从1开始),然后是该大学学生信仰的不同宗教的最大数量。
Sample Input
10 9 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 1 10 10 4 2 3 4 5 4 8 5 8 0 0
Sample Output
Case 1: 1 Case 2: 7
以第一个样例为例:首先我们假设九个学生都是独立的集合{1},{2},...,{9}。第一个关系表示1号和2号学生信仰一个宗教,所以1所在的集合应该和2所在的集合合并。所以集合就变成了{1,2},{3},...,{9}。第二个关系表示1和3信仰一个宗教,所以合并1和3所在的集合{1,2,3},{4},...,{9}。依次类推,最后集合数目为1,所以全部学生只信仰一个宗教。
怎么实现呢?引如disjoint_set[ ]数组,首先让disjoint_set[i]=i;如果1和2要合并成为一个集合,让disjoint_set[1]=disjoint_set[2]即可;
那么如何表示不同的学生信仰一个宗教,也就是在一个集合中呢?比如集合{1,2,3}如果disjoint_set[1]==disjoint_set[2]==disjoint_set[3]则表示1,2,3这三个元素在一个集合中。
代码实现:
1 #include <iostream> 2 #include <cstdio> 3 #include <vector> 4 5 using namespace std; 6 int disjoint_set[50010]; 7 int a[50010]; 8 int findset(int x){ 9 return disjoint_set[x]!=x?disjoint_set[x]=findset(disjoint_set[x]):x; 10 } 11 12 int readint(){ 13 int x;scanf("%d",&x);return x; 14 } 15 16 int main(){ 17 int n,m; 18 int ss=0; 19 while(~scanf("%d%d",&n,&m)){ 20 ss++; 21 if(n==0&&m==0)break; 22 for(int i=1;i<=n;i++){ 23 disjoint_set[i]=i; 24 a[i]=0; 25 } 26 for(int i=0,v,c;i<m;i++){ 27 scanf("%d%d",&v,&c); 28 int x=findset(v); 29 int y=findset(c); 30 if(x!=y){ 31 disjoint_set[x]=y; 32 } 33 } 34 for(int i=1;i<=n;i++){ 35 a[findset(i)]=1; 36 } 37 int sum=0; 38 for(int i=1;i<=n;i++){ 39 if(a[i]==1)sum++; 40 } 41 printf("Case %d: ",ss); 42 printf("%d\n",sum); 43 } 44 return 0; 45 }