整理一下最近的题解,这是三个并查集的题目,分别是:
POJ 1182 食物链
POJ 1611 The Suspects
POJ 2524 Ubiquitous Religions
POJ 1182 食物链
这个题有一个很棒的套路,那就是用一个并查集来维护三个集合内的元素,可以使这个并查集扩大n倍,然后按照元素是属于哪一个集合来进行运算,放入并查集对应范围内的对应位置。代码如下:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 0x7ffffff; int pre[maxn]; int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); } void unite(int x, int y) { x = find(x); y = find(y); if(x != y) { pre[y] = x; } } int main() { int N, K, D, x, y; scanf("%d %d", &N, &K); int mm = N * 3; for(int i = 0; i < mm; i++) { pre[i] = i; } int ans = 0; while(K--) { scanf("%d %d %d", &D, &x, &y); if(x > N || y > N) { ans++; continue; } if(D == 1) { //同类 if(find(x) == find(y+N) || find(x) == find(y+2*N)) { ans++; //矛盾 } else { unite(x, y); //放入A unite(x+N, y+N); //放入B unite(x+2*N, y+2*N);//放入C } } else if(D == 2) { //不同类 if(find(x) == find(y) || find(x) == find(y+2*N)) { ans++; //矛盾 } else { unite(x, y+N); //x放入A,y放入B unite(x+N, y+2*N); //x放入B,y放入C unite(x+2*N, y); //x放入B,y放入A } } } printf("%d ", ans); }
POJ 1611 The Suspects
这道题可以在输入的时候进行处理,单独读入一个元素后,再循环读入剩下的,再与每一个元素的前者进行合并操作(如果不是同一个father)。在并查集的unite操作中来动态统计合并到某两个元素的时候此时的suspects的数量,这样的话,只需要找到0号的father就可以找到suspects的总人数了(默认0是患病的)。代码如下:
1 #define _CRT_SECURE_NO_WARNINGS 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstring> 5 #include <iostream> 6 7 using namespace std; 8 9 const int maxn = 30010; 10 int father[maxn]; 11 int num[maxn]; 12 int n, m; 13 14 void init() { 15 for (int i = 0; i < maxn; i++) { 16 father[i] = i; 17 num[i] = 1; 18 } 19 } 20 int find(int x) { 21 return x == father[x] ? x : father[x] = find(father[x]); 22 } 23 void unite(int x, int y) { 24 x = find(x); 25 y = find(y); 26 if(x != y) { 27 father[x] = y; 28 num[y] += num[x]; 29 } 30 } 31 32 int main(int argc, char *argv[]) { 33 while (~scanf("%d %d", &n, &m) && n + m) { 34 init(); 35 int group; 36 int tmp[maxn]; 37 int ans = 0; 38 for (int ii = 0; ii < m; ii++) { 39 memset(tmp, 0, sizeof(tmp)); 40 scanf("%d", &group); 41 scanf("%d", &tmp[0]); 42 for (int i = 1; i < group; i++) { 43 scanf("%d", &tmp[i]); 44 if (father[tmp[i]] != father[tmp[i - 1]]) { 45 unite(tmp[i], tmp[i - 1]); 46 } 47 } 48 } 49 // for (int i = 0; i < n; i++) { 50 // if (father[i] == 0) { 51 // ans++; 52 // } 53 // } 54 printf("%d ", num[find(0)]); 55 } 56 return 0; 57 }
POJ 2524 Ubiquitous Religions
额外加一个vis来判断某信仰是否被遍历到,利用并查集“合并过的两个元素其中一个元素的father必定不是本身”这一性质统计一共有多少个集合。代码如下:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 50010; 8 int pre[maxn]; 9 int vis[maxn]; 10 int n, m; 11 12 int find(int x) { 13 return x == pre[x] ? x : pre[x] = find(pre[x]); 14 } 15 16 void unite(int x, int y) { 17 x = find(x); 18 y = find(y); 19 if(x != y) { 20 pre[y] = x; 21 } 22 } 23 inline void init() { 24 for(int i = 0; i < maxn; i++) { 25 pre[i] = i; 26 } 27 } 28 29 int main() { 30 int kase = 1; 31 while(~scanf("%d %d", &n, &m) && n+m) { 32 init(); 33 memset(vis, 0, sizeof(vis)); 34 int a, b; 35 int cnt = 0; 36 for(int i = 0; i < m; i++) { 37 scanf("%d %d", &a, &b); 38 vis[a] = 1; 39 vis[b] = 1; 40 unite(a, b); 41 } 42 int k = 0; 43 // for(int i = 1; i <= n; i++) { 44 // cout << pre[i] << " "; 45 // } 46 // cout << endl; 47 for(int i = 1; i <= n; i++) { 48 if(vis[i] && pre[i] == i) { 49 cnt++; 50 } 51 if(!vis[i]) { 52 cnt++; 53 } 54 } 55 printf("Case %d: %d ", kase++, cnt); 56 } 57 return 0; 58 }