1. 並查集是什么
並查集是一種用來管理元素分組情況的數據結構。並查集可以高效地進行如下操作。不過需要注意並查集雖然可以進行合並操作,但是無法進行分割操作。
-
查詢元素a和元素b是否屬於同一組。
-
合並元素a和元素b所在的組。
2. 並查集的結構
並查集也是使用樹形結構實現。不過,不是二叉樹。
每個元素對應一個節點,每個組對應一棵樹。在並查集中,哪個節點是哪個節點的父親以及樹的形狀等信息無需多加關注,整體組成一個樹形結構才是重要的。
3. 代碼
1 /* 2 6 3 1 1 4 2 1 5 5 1 6 6 6 7 4 6 8 7 4 9 */ 10 #define _CRT_SECURE_NO_WARNINGS 11 #include <iostream> 12 #include <cstring> 13 #include <cstdio> 14 #include <cstdlib> 15 using namespace std; 16 17 const int maxn = 1000 + 100; 18 int par[maxn]; //父親, 當par[x] = x時,x是所在的樹的根 19 int Rank[maxn]; //樹的高度 20 21 //初始化n個元素 22 void init(int n) 23 { 24 for (int i = 0; i < n; i++) { 25 par[i] = i; 26 Rank[i] = 0; 27 } 28 } 29 30 //查詢樹的根 31 int find(int x) { 32 if (par[x] == x) { 33 return x; 34 } 35 else { 36 return par[x] = find(par[x]); 37 } 38 } 39 40 //合並x和y所屬集合 41 void unite(int x, int y) { 42 x = find(x); 43 y = find(y); 44 if (x == y) return; 45 46 if (Rank[x] < Rank[y]) { 47 par[x] = y; 48 } else { 49 par[y] = x; 50 if (Rank[x] == Rank[y]) Rank[x]++; //如果x,y的樹高相同,就讓x的樹高+1 51 } 52 } 53 54 //判斷x和y是否屬於同一個集合 55 bool same(int x, int y) { 56 return find(x) == find(y); 57 } 58 59 int main() 60 { 61 int n; 62 scanf("%d", &n); 63 init(n); 64 65 int data, p; 66 cout << "輸入數據: \n"; 67 for (int i = 0; i < n; i++) { 68 scanf("%d%d", &data, &p); 69 par[data] = p; 70 Rank[p]++; 71 } 72 73 cout << "輸入合並集合: \n"; 74 int p1, p2; 75 cin >> p1 >> p2; 76 unite(p1, p2); 77 cout << "查詢是否屬於一個集合: \n"; 78 cin >> p1 >> p2; 79 80 if (same(p1, p2)) { 81 puts("same"); 82 } 83 else { 84 puts("diff"); 85 } 86 87 return 0; 88 }
食物鏈(POJ 1182)
Time Limit: 1000MS | Memory Limit: 10000K | |
---|---|---|
Total Submissions: 67690 | Accepted: 20010 |
Description
動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A吃B, B吃C,C吃A。 現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們並不知道它到底是哪一種。 有人用兩種說法對這N個動物所構成的食物鏈關系進行描述:
-
第一種說法是"1 X Y",表示X和Y是同類。
-
第二種說法是"2 X Y",表示X吃Y。
此人對N個動物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。 1) 當前的話與前面的某些真的話沖突,就是假話; 2) 當前的話中X或Y比N大,就是假話; 3) 當前的話表示X吃X,就是假話。 你的任務是根據給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數。
Input
第一行是兩個整數N和K,以一個空格分隔。 以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。 若D=1,則表示X和Y是同類。 若D=2,則表示X吃Y。
Output
只有一個整數,表示假話的數目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
Source
解析
由於N和K 很大,所以必須高效地維護動物之間關系,並快速判斷是否產生了矛盾。並查集是維護“屬於同一組”的數據結構,但是本題中,並不是只有屬於同一類的信息,還有捕食關系的存在。因此需要思考如何維護這些關系。
對於每只動物i創建3個元素 i - A, i - B, i - C, 並用這3 x N個元素建立並查集。這個並查集維護如下信息:
i-x表示 “i屬於種類x"
並查集里每個組表示組內所有元素代表的情況都同時發生或不發生。
例如:如果i-A和j-B在同一個組里,就表示如果i屬於種類A那么j一定屬於種類B,如果i屬於種類B那么j一定屬於種類A。因此,得出下面的操作。
第一種,x和y屬於同一種類·······合並x-A和y-A、x-B和y-B、x-C和y-C。
第二種,x吃y······························合並x-A和y-B、x-B和y-C、x-C和y-A。
不過在合並前,需要先判斷合並是否會產生矛盾。例如在第一種信息的情況下,需要檢查(x-A, y-B) ||(x-A, y-C)是否在同一組等。
代碼:
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int maxn = 100000 * 3 + 500; //輸入(T是信息的類型) int N, K; //N-N種類, K-K條信息 int T[maxn], X[maxn], Y[maxn]; //在這里省略了並查集部分代碼 int par[maxn]; int Rank[maxn]; void solve(); void init(int n); int find(int x); void unite(int x, int y); bool same(int x, int y); void init(int n) { for (int i = 0; i < n; i++) { par[i] = i; Rank[i] = 0; } } //查詢樹根 int find(int x) { if (par[x] == x) { return x; } else { return par[x] = find(par[x]); } } //合並x和y所屬的集合 void unite(int x, int y) { x = find(x); y = find(y); if (x == y) return; if (Rank[x] < Rank[y]) { par[x] = y; } else { par[y] = x; if (Rank[x] == Rank[y]) Rank[x]++; } } bool same(int x, int y) { return find(x) == find(y); } void solve() { //初始化並查集 //元素x, x + N, x + 2 * N 分別代碼 x-A, x-B, x-C init(N * 3); int ans = 0; for (int i = 0; i < K; i++) { int t = T[i]; int x = X[i] - 1, y = Y[i] - 1; //把輸入變成0,...N-1的范圍 //不正確的編號 if (x < 0 || N <= x || y < 0 || N <= y) { ans++; continue; } if (t == 1) { // "x和y屬於同一類"的信息 if (same(x, y + N) || same(x, y + 2 * N)) { ans++; } else { unite(x, y); unite(x + N, y + N); unite(x + N * 2, y + N * 2); } } else { //"x吃y"的信息 if (same(x, y) || same(x, y + 2 * N)) { //A和A,A和C不能相等 ans++; } else { unite(x, y + N); unite(x + N, y + 2 * N); unite(x + 2 * N, y); } } } printf("%d\n", ans); } int main() { cin >> N >> K; for (int i = 0; i < K; i++) { scanf("%d%d%d", &T[i], &X[i], &Y[i]); } solve(); return 0; }