-
概述
並查集是一種數據結構,主要處理一些不相交的集合的合併問題。就是集合的合併操作。經典的例子有:連通子圖、最小生成樹Kruskal算法和最近公共祖先等。並查集主要操作有初始化、合併、查詢。 -
優化
合併優化:在合併兩個不同集合的元素時,需要找到他們的根結點,將根結點合併。在合併的時候將高度較小的樹合併在高度較高的樹下,能有效減小樹的高度,有利於查詢。路徑壓縮:在直接進行簡單合併的情況下,在某個集合中會形成樹狀的結構,這種結構不利於根結點的查詢(需要逐層向上查詢,速度比較慢),所以在許多情況下需要在查詢根結點的同時,將同一集合中的各元素的雙親直接更改爲根結點,這樣在查詢的時候就會快很多,這個操作稱爲路徑壓縮。
並不是在所有情況下都需要進行這兩種優化,例如接下來龍珠的的題目中,合併後樹的高度可以作爲龍珠搬運的次數,在時間允許的範圍內可以簡化代碼,思路也比較好理解。
-
練習
How Many Tables
Ubiquitous Religions
The Suspects
Find them, Catch them
Wireless Network
A Bug’s Life
Cube Stacking
食物鏈
Dragon Balls
More is better
小希的迷宮
Is It A Tree?
Farm Irrigation題意:n個人參加晚宴,完全不認識的兩個人不能被分配在同一餐桌,認識具有傳遞性:A認識B B認識C,那麼A和C也認識,所以A、B、C可以在同一張餐桌上。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; int pe[1050]; int high[1050];//樹的高度 int N, M; int ans; void init_set() { for(int i=1; i<=N; i++) { pe[i] = i; high[i] = 0; } } int find_set(int x) { int root = x; while(root!= pe[root]) root = pe[root]; int i = x, temp; while(pe[i]!=i) { temp = pe[i]; pe[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(high[A] == high[B]) { pe[B] = A; high[A]++; } if(high[A]>high[B]) pe[B] = A; else pe[A] = B; } int main() { int T; cin>> T; while(T--) { scanf("%d%d", &N, &M); init_set(); while(M--) { int A, B; scanf("%d%d", &A, &B); union_set(A, B); } ans = 0; for(int i=1; i<=N; i++) { if(pe[i] == i) ans++; } printf("%d\n", ans); } }
題意:你的學校有n名學生(0 < n <= 50000),你不太可能詢問每個人的宗教信仰,因爲他們不太願意透露。但是當你同時找到2名學生,他們卻願意告訴你他們是否信仰同一宗教,你可以通過很多這樣的詢問估算學校裏的宗教數目的上限。你可以認爲每名學生只會信仰最多一種宗教。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; int pe[50010]; int high[50010]; int N, M; int ans; void init_set() { for(int i=1; i<=N; i++) { pe[i] = i; high[i] = 0; } } int find_set(int x) { int root = x; while(root!= pe[root]) root = pe[root]; int i = x, temp; while(pe[i]!=i) { temp = pe[i]; pe[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(high[A] == high[B]) { pe[B] = A; high[A]++; } if(high[A]>high[B]) pe[B] = A; else pe[A] = B; } int main() { int c=1; while(scanf("%d%d", &N, &M)) { if(N==0 && M==0) break; init_set(); while(M--) { int A, B; scanf("%d%d", &A, &B); union_set(A, B); } ans = 0; for(int i=1; i<=N; i++) { if(pe[i] == i) ans++; } printf("Case %d: %d\n", (c++), ans); } }
題意:有n個學生,m個社團。(0<n<=3000)(0<=m<=500),學生學號從0到n-1。已知0號學生已感染病毒。每個學生可能參加多個社團。
輸入:第一行,兩個整數n,m;(當n=m=0時,結束),接下來的m行輸入一個數k(這個社團的人數),後跟學生的編號。
輸出:對於每一個例子,輸出一個數代表可能感染的總人數,佔一行。
明顯是有和零號有接觸的人都有事,只要將有聯繫的人分入同一組即可。#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; int pe[50010]; int N, M; int ans; void init_set() { for(int i=0; i<=N; i++) { pe[i] = i; } } int find_set(int x) { int root = x; while(root!= pe[root]) root = pe[root]; int i = x, temp; while(pe[i]!=i) { temp = pe[i]; pe[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(B==0) pe[A] = B; else pe[B] = A; } int main() { while(scanf("%d%d", &N, &M)) { if(N==0 && M==0) break; init_set(); while(M--) { int k, first, another; scanf("%d%d", &k,&first); for(int i=1; i<k; i++) { scanf("%d", &another); union_set(first, another); } } ans = 0; int r = find_set(0); for(int i=0; i<=N; i++) { if(r == find_set(i)) ans++; } printf("%d\n", ans); } }
題意:第一行表示用例組數,第二行表示人數和信息組數有兩個幫派,D a b表示a和b屬於不同幫派,A a b表示要你回答a和b是屬於同一幫派、不同幫派還是並不確定。
思路1:N個人分屬於兩個幫派。所以我們可以初始化一個N*2的並查集,然後給定D a b就unit(a,b+n),unit(b,a+n)。
思路2:給的是a,b的敵對關係,可以用數組enemy把a的敵人存起來,然後如果a,x是敵人,把s[a]和x用並查集合並,再更新s[a]=x;
#include<iostream> #include<cstdio> #include<string.h> using namespace std; #define MAX 100010 int high[MAX]; int s[MAX], enemy[MAX]; int t, n, m; char c; int a, b; void init_set() { for(int i=0; i<=n+1; i++) { // enemy[i] = 0; s[i] = i; high[i] = 0; } } int find_set(int x) { int root = x; while(root!= s[root]) root = s[root]; int i = x, temp; while(s[i]!=i) { temp = s[i]; s[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(high[A] == high[B]) { s[B] = A; high[A]++; } if(high[A]>high[B]) s[B] = A; else s[A] = B; } int main() { scanf("%d", &t); while(t--) { scanf("%d%d", &n, &m); init_set(); memset(enemy, 0, sizeof(enemy)); while(m--) { getchar(); scanf("%c%d%d", &c, &a, &b); if(c=='D') { if(enemy[a]) { union_set(enemy[a], b); } if(enemy[b]) { union_set(enemy[b], a); } enemy[a] = b; enemy[b] = a; } else { if(find_set(a)==find_set(b)) { printf("In the same gang.\n"); } else if(find_set(a)==find_set(enemy[b])) { printf("In different gangs.\n"); } else { printf("Not sure yet.\n"); } } } } return 0; }
題意:給你n個電腦的位置,和信號覆蓋的的半徑d,一開始所有的電腦都壞了,你每單位時間可以進行 O(修復一臺電腦),S(檢查兩臺電腦是否聯通,間接聯通也算),只有修理好的計算機才能連通,如果兩臺計算機的距離不超過d,則兩臺電腦之間可以直接連接。
#include<iostream> #include<cstdio> #include<string.h> using namespace std; #define MAX 1010 typedef struct { int x, y; }computer; bool fcp[MAX]; computer cp[MAX]; int high[MAX]; int s[MAX]; int n, d, dd; int p, q; char c; void init_set() { for(int i=0; i<=n+1; i++) { s[i] = i; high[i] = 0; } } int find_set(int x) { int root = x; while(root!= s[root]) root = s[root]; int i = x, temp; while(s[i]!=i) { temp = s[i]; s[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(high[A] == high[B]) { s[B] = A; high[A]++; } if(high[A]>high[B]) s[B] = A; else s[A] = B; } int main() { memset(fcp, false, sizeof(fcp)); scanf("%d%d", &n, &d); dd = d*d; init_set(); for(int i=1; i<=n; i++) { scanf("%d%d", &cp[i].x, &cp[i].y); } getchar(); while((scanf("%c", &c))!=EOF) { if(c=='S') { scanf("%d%d", &p, &q); if((find_set(p)==find_set(q))) printf("SUCCESS\n"); else printf("FAIL\n"); } else { bool flag=true; scanf("%d", &p); for(int i=1; i<=n; i++) { if(fcp[i] && i!=p) { int xx = (cp[p].x-cp[i].x)*(cp[p].x-cp[i].x); int yy = (cp[p].y-cp[i].y)*(cp[p].y-cp[i].y); if((xx+yy)<=dd) { union_set(i, p); } } } fcp[p]=true; } getchar(); } return 0; }
題意:t組數據,n個蟲子,m組相互喜愛的關係,蟲子分爲雌雄兩種,每個蟲子只有一個性別,問是否存在同性戀的蟲子。
與Find them, Catch them思路相似。#include<iostream> #include<cstdio> #include<string.h> using namespace std; #define MAX 2010 int high[MAX]; int s[MAX], enemy[MAX]; int t, n, m; bool flag; int a, b; void init_set() { for(int i=0; i<=n+1; i++) { s[i] = i; high[i] = 0; } } int find_set(int x) { int root = x; while(root!= s[root]) root = s[root]; int i = x, temp; while(s[i]!=i) { temp = s[i]; s[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(high[A] == high[B]) { s[B] = A; high[A]++; } if(high[A]>high[B]) s[B] = A; else s[A] = B; } int main() { scanf("%d", &t); for(int i=1; i<=t; i++) { scanf("%d%d", &n, &m); init_set(); memset(enemy, 0, sizeof(enemy)); flag = true; while(m--) { scanf("%d%d", &a, &b); if(flag) { if(find_set(a)==find_set(b)) { flag = false; } else { if(enemy[a]) { union_set(enemy[a], b); } if(enemy[b]) { union_set(enemy[b], a); } enemy[a] = b; enemy[b] = a; } } } printf("Scenario #%d:\n", i); if(flag) printf("No suspicious bugs found!\n\n"); else printf("Suspicious bugs found!\n\n"); } return 0; }
題意:有n個從1到n編號的箱子,將每個箱子當做一個棧,對這些箱子進行p次操作,每次操作分別爲以下兩種之一:
1.輸入 M x y:表示將編號爲x的箱子所在的棧放在編號爲y的箱子所在棧的棧頂.
2.輸入 C x:計算編號爲x的所表示的棧中在x號箱子下面的箱子數目.帶權並查集。(待完善)
#include<iostream> #include<cstdio> #include<string.h> using namespace std; #define MAX 30010 int s[MAX], val[MAX], dis[MAX]; int t; char c; int a, b; void init_set() { for(int i=0; i<MAX; i++) { dis[i] = 0; s[i] = i; val[i] = 1; } } int find_set(int x) { if(x!=s[x]) { int temp = s[x]; s[x] = find_set(s[x]); dis[x] += dis[temp]; } return s[x]; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(A!=B) { s[B] = A; dis[B] = val[A]; val[A] += val[B]; } } int main() { scanf("%d", &t); init_set(); while(t--) { getchar(); scanf("%c", &c); if(c=='M') { scanf("%d%d", &a, &b); union_set(a, b); } else { scanf("%d", &a); int x=find_set(a); printf("%d\n", val[x]-dis[a]-1); } } return 0; }
題意:動物王國中有三類動物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),輸出假話的總數。
與Find them, Catch them相似,用的思路1的思想,開三倍大的數組。參考#include<iostream> #include<cstdio> #include<string.h> using namespace std; #define MAX 50010 int s[3*MAX]; //x<-x+n,x+n<-x+2*n,x+2*n<-x int n, k, d; int x, y; int ans; void init_set() { for(int i=1; i<3*MAX; i++) { s[i] = i; } } int find_set(int x) { int root = x; while(root!= s[root]) root = s[root]; int i = x, temp; while(s[i]!=i) { temp = s[i]; s[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); s[A] = B; } int main() { scanf("%d%d", &n, &k); init_set(); while(k--) { scanf("%d%d%d", &d, &x, &y); if(x>n || y>n) { ans++; continue ; } if(d==1) { if(find_set(x+2*n)==find_set(y) || find_set(x)==find_set(y+2*n)) ans++; else { union_set(x, y); union_set(x+n, y+n); union_set(x+2*n, y+2*n); } } else { if(x==y || find_set(x)==find_set(y+2*n) || find_set(x)==find_set(y)) ans++; else { union_set(x+2*n, y); union_set(x+n, y+2*n); union_set(x, y+n); } } } printf("%d", ans); return 0; }
題意:先說每個城市和龍珠都有編號,對應的第i個龍珠放在第i個城市。T A B表示把A號龍珠所在的城市的所有龍珠全部搬運到B號龍珠所在的城市。
Q A表示要求出X(第A號龍珠所在的城市編號),Y(第X號城市存放的龍珠個數),Z(第A號龍珠被搬運的次數)。帶權並查集。但是可以知道,龍珠每搬運一次,在並查集中的深度就會加一(未優化),所以在不優化並查集的前提下,龍珠的深度就是搬運次數。
#include<iostream> #include<cstdio> #include<string.h> using namespace std; #define MAX 10010 //int trans[MAX]; int s[MAX], sum[MAX]; int ans[10010][3]; int t; int n, q; int a, b; int deep; void init_set() { for(int i=1; i<=n; i++) { s[i] = i; sum[i] = 1; // trans[i] = 0; } } int find_set(int x) { int root = x; while(root!= s[root]) { root = s[root]; deep++; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(A!=B) { s[A] = B; sum[B] += sum[A]; sum[A] = 0; } } int main() { scanf("%d", &t); for(int i=1; i<=t; i++) { printf("Case %d:\n", i); scanf("%d%d", &n, &q); init_set(); while(q--) { char c; getchar(); scanf("%c", &c); if(c=='T') { scanf("%d%d", &a, &b); union_set(a, b); } else { scanf("%d", &a); deep = 0; b = find_set(a); printf("%d %d %d\n", b, sum[b], deep); } } } return 0; }
題意:要找一些人完成一項工程。要求最後挑選出的人之間都是朋友關係,可以說直接的,也可以是間接地。問最多可以挑選出幾個人(最少挑一個)。
在基礎的並查集上加個數組記錄集合的數量。#include<iostream> #include<cstdio> using namespace std; #define MAX 10000005 int sum[MAX]; int s[MAX]; int ans; int n; int a, b; void init_set() { for(int i=1; i<MAX; i++) { s[i] = i; sum[i] = 1; } } int find_set(int x) { int root = x; while(root!= s[root]) root = s[root]; int i = x, temp; while(s[i]!=i) { temp = s[i]; s[i] = root; i = temp; } return root; } void union_set(int A, int B) { A = find_set(A); B = find_set(B); if(A!=B) { s[B] = A; sum[A] += sum[B]; if(sum[A]>ans) ans = sum[A]; } } int main() { while((scanf("%d", &n))!=EOF) { init_set(); if(n==0) { printf("1\n"); continue; } ans=0; while(n--) { scanf("%d%d", &a, &b); union_set(a, b); } printf("%d\n", ans); } return 0; }
思路:每輸入一組數據,都要對其進行連接,如果兩個點find(a)==find(b),那麼說明他們已經是一個集合的了,如果再連接a,b兩個點,就會構成迴路,這裏也就是要輸出no.
坑點:給出的圖不一定是連通圖,可能有多個連通分量。#include<iostream> #include<cstdio> #include<cstdlib> #include<string.h> using namespace std; #define MAX 100010 bool vis[MAX]; int s[MAX]; int a, b; bool flag; char c; void init() { for(int i=0; i<MAX; i++) { s[i] = i; vis[i] = false; } } int find_set(int x) { int root=x; while(root!=s[root]) root = s[root]; int i = x, temp; while(s[i]!=i) { temp = s[i]; s[i] = root; i = temp; } return root; } void union_set(int a, int b) { a = find_set(a); b = find_set(b); if(a==b) flag = false; else s[a] = b; vis[a] = true; vis[b] = true; } int main() { while(~scanf("%d%d", &a, &b)) { flag = true; if(a==-1 && b==-1) break; if(a==0 && b==0) { printf("Yes\n"); continue; } if(a==0 || b==0) flag = false; init(); union_set(a, b); while(scanf("%d%d", &a, &b)&&a&&b) { if(!flag) continue; else union_set(a, b); } int r=0; if(flag) { for(int i=1; i<MAX; i++) { if(vis[i] && find_set(i)==i) { r++; } } } if(flag && r==1) printf("Yes\n"); else printf("No\n"); } return 0; }
題意:根據給出的數據判斷是否能構成一棵樹。特判0 0 是一棵空樹。
#include<iostream> #include<cstdio> #include<cstdlib> #include<string.h> using namespace std; #define MAX 1000000 bool vis[MAX]; int mx; int s[MAX]; int a, b; bool flag; char c; void init() { flag = true; for(int i=0; i<MAX; i++) { s[i] = i; vis[i] = false; } } int find_set(int x) { int root=x; while(root!=s[root]) root = s[root]; int i = x, temp; while(s[i]!=i) { temp = s[i]; s[i] = root; i = temp; } return root; } void union_set(int a, int b) { vis[a] = true; vis[b] = true; int aa = find_set(a); int bb = find_set(b); if(aa==bb || bb!=b) flag=false; else s[bb] = aa; } int main() { for(int i=1; ; i++) { scanf("%d%d", &a, &b); if(a<0 && b<0) break; if(a==0 && b==0) { printf("Case %d is a tree.\n", i); continue; } init(); union_set(a, b); while(scanf("%d%d", &a, &b)&&a&&b) { if(!flag) continue; else union_set(a, b); } int root = 0; if(flag) { for(int i=0; i<MAX; i++) { if(vis[i] && find_set(i)==i) { root++; } } } if(flag && root==1) printf("Case %d is a tree.\n", i); else printf("Case %d is not a tree.\n", i); } return 0; }
題意:給你不同形狀 的水管,看哪些水管聚類連通,分成 N 堆答案就是 N。 更容易想到DFS
#include<iostream> #include<cstdlib> #include<cstdio> using namespace std; #define MAX 55 typedef struct { int u, d, l, r; }square; int m ,n; char c; int ans; int farm[MAX][MAX]; square squares[11]={{1,0,1,0}, {1,0,0,1}, {0,1,1,0}, {0,1,0,1}, {1,1,0,0}, {0,0,1,1}, {1,0,1,1}, {1,1,1,0}, {0,1,1,1}, {1,1,0,1}, {1,1,1,1}}; int dir[4][2]={{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; //上左下右 bool check(int i, int j) { if(i<0 || i>=m || j<0 || j>=n) return false; else return true; } void DFS(int i, int j) { int ii, jj, x, p; p = farm[i][j]; farm[i][j] = -1; ii = i+dir[0][0]; jj = j+dir[0][1]; if(check(ii, jj) && farm[ii][jj]!=-1) { x = squares[p].u + squares[farm[ii][jj]].d; if(x == 2) DFS(ii, jj); } ii = i+dir[1][0]; jj = j+dir[1][1]; if(check(ii, jj) && farm[ii][jj]!=-1) { x = squares[p].l + squares[farm[ii][jj]].r; if(x == 2) DFS(ii, jj); } ii = i+dir[2][0]; jj = j+dir[2][1]; if(check(ii, jj) && farm[ii][jj]!=-1) { x = squares[p].d + squares[farm[ii][jj]].u; if(x == 2) DFS(ii, jj); } ii = i+dir[3][0]; jj = j+dir[3][1]; if(check(ii, jj) && farm[ii][jj]!=-1) { x = squares[p].r + squares[farm[ii][jj]].l; if(x == 2) DFS(ii, jj); } } int main() { while(scanf("%d%d", &m, &n)) { if(m==-1 && n==-1) break; if(m==0 && n==0) { printf("0\n"); continue; } ans = 0; for(int i=0; i<m; i++) { string s; cin>> s; for(int j=0; j<n; j++) farm[i][j] = s[j]-'A'; } for(int i=0; i<m; i++) { for(int j=0; j<n; j++) { if(farm[i][j]!=-1) { ans++; DFS(i, j); } } } printf("%d\n", ans); } }
並查集
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.