题目链接:http://poj.org/problem?id=1611
题意:给定n个人和m个群,接下来是m行,每行给出该群内的人数以及这些人所对应的编号。需要统计出跟编号0的人有直接或间接关系的人数总共有多少。
这又是一条并查集的应用。难点是如何统计出与0有关系的总人数,即包含0的集合内元素的总个数。我的方法是用了两次merge,第一次merge单纯地将同一群内的元素连边,当然该群内的元素的祖先有可能是别的群内的元素,连边的规则是大的元素指向小的元素;第二次merge则把第一次筛选出来的集合中将元素与它的祖先再合并。目的是为了每一个集合内的元素都指向同一个代表。
以第一个数据测试来说,第二行的 2 1 2,把编号1和2的人连边,此时p[1] = 1, p[2] = 1;
第四行的 2 0 1,把编号0和1的人连边,此时p[0] = 0,p[1] = 0;第五行的 2 99 2,结果p[99] = 0,p[2] = 1。(p[99] 指向元素2的祖先,而2的祖先1的祖先为0,因此p[99] = 0)。处理完问题出现了,第一次merge并不能使p[2]指向0,它还是保持1,因此第二次merge就有必要设置了。它使得p[2] = 0。由于代表为0的集合上所有的元素都指向0,因此最后统计哪些祖先是0的集合个数就是答案了。
1 #include <iostream> 2 using namespace std; 3 4 const int maxn = 30000 + 5; 5 int p[maxn]; 6 7 int find(int x) 8 { 9 while (x != p[x]) 10 x = p[x]; 11 return x; 12 } 13 14 void merge(int x, int y) 15 { 16 int fx = find(x); 17 int fy = find(y); 18 if (fx > fy) // 从一开始就保证大的元素指向小的元素,即大的元素的祖先是小的元素 19 p[fx] = fy; 20 else 21 p[fy] = fx; 22 } 23 24 void merge1(int x, int y) // 第二次merge很关键 25 { 26 int fx = find(x); 27 int fy = find(y); 28 if (fx > fy) // 数组下标与第一次merge是不同的!!! 29 p[x] = fy; 30 else 31 p[y] = fx; 32 } 33 34 int main() 35 { 36 int i, j, n, m, t, x, k; 37 while (scanf("%d%d", &n, &m) != EOF && (m || n)) 38 { 39 if (m == 0) // 特殊情况没有群,此时只有0这个学生是嫌疑犯 40 printf("1\n"); 41 else 42 { 43 for (i = 0; i < n; i++) 44 { 45 p[i] = i; 46 } 47 for (i = 0; i < m; i++) 48 { 49 scanf("%d", &k); 50 scanf("%d", &t); // 每组中第一个人要单独输入,为了下面的merge操作方便 51 for (j = 1; j < k; j++) 52 { 53 scanf("%d", &x); 54 merge(t, x); 55 // printf("p[%d] = %d, p[%d] = %d\n", t, p[t], x, p[x]); 56 t = x; 57 } 58 } 59 int cnt = 1; // 编号为0这个人 60 for (i = 1; i < n; i++) 61 { 62 if (p[i] != i) // 第一轮merge后的再一次处理 63 { 64 merge1(p[i], i); 65 // printf("p[%d] = %d\n", i, p[i]); 66 if (p[i] == 0) 67 cnt++; 68 } 69 } 70 printf("%d\n", cnt); 71 } 72 } 73 return 0; 74 }