【並查集】模板 + 【HDU 1213、HDU 1232、POJ 2236、POJ 1703】例題詳解
不想看模板,想直接看題目的請戳下面目錄:
目錄:
HDU 1213 How Many Tables【傳送門】
HDU 1232 暢通工程 【傳送門】
POJ 2236 Wireless Network 【傳送門】
POJ 1703 Find them, Catch them 【傳送門】
先上模板:
1 #define MAXN 根據編號需要 2 int per[MAXN],rank[MAXN]; 3 4 void init(int n) 5 { 6 int i; 7 for(i=1;i<=n;i++) 8 { 9 per[i]=i;rank[i]=0 10 } 11 12 } 13 14 int find(int x) 15 { 16 if(x==per[x]) 17 return x; 18 return per[x]=find(per[x]); 19 } 20 21 void unite(int x,int y) 22 { 23 x=find(x); 24 y=find(y); 25 if(x==y) 26 return; 27 if(rank[x]>rank[y]) 28 per[y]=x; 29 else 30 { 31 per[x]=y; 32 if(rank[x]==rank[y]) 33 rank[y]+=1; 34 } 35 } 36 37 bool same(int x,int y) 38 { 39 return find(x)==find(y); 40 }
並查集詳解請訪問:(通俗易懂解釋)
https://blog.csdn.net/lesileqin/article/details/96703143
接下來來看幾個例子:
HDU 1213 How Many Tables【傳送門】
題目大意:有一個人過生日,請到了他的諸多朋友,但是這些朋友之間有的認識,有的不認識。這個人想盡可能的把相互之間認識的人湊到一張桌子上,不認識的人則去另一張桌子。朋友們互相認識的規則是:比如A認識B,B認識C,那麼A,B,C就可以湊到一桌子上。現在問:他的朋友們以這樣的規則能湊夠幾桌。
解題思路:運用並查集,把相互認識的人都聯合一下,到最後計算陣列中有幾個per值等於自身的人就可以了。
1 #include<iostream> 2 3 using namespace std; 4 5 int f[1005]; 6 7 void init(int n) 8 { 9 for(int i=1;i<=n;i++) 10 f[i]=i; 11 } 12 13 int find(int a) 14 { 15 while(a!=f[a]) 16 { 17 a=f[a]; 18 } 19 return a; 20 } 21 22 void Combin(int a,int b) 23 { 24 int ta,tb; 25 ta=find(a); 26 tb=find(b); 27 28 if(ta!=tb) 29 f[ta]=tb; 30 } 31 32 int answer(int n) 33 { 34 int sum=0; 35 for(int i=1;i<=n;i++) 36 if(f[i]==i) 37 sum++; 38 return sum; 39 } 40 41 int main() 42 { 43 44 int t; 45 cin >> t; 46 while(t--) 47 { 48 int n,m; 49 cin >> n >> m; 50 init(n); 51 int a,b; 52 for(int i=0;i<m;i++) 53 cin >> a >> b,Combin(a,b); 54 cout << answer(n) << endl ; 55 } 56 57 return 0; 58 }
HDU 1232 暢通工程 【傳送門】
此處略去題目大意……
解題思路:其實這個題目和上一個題異曲同工,就是變換了一下條件,我們假設村子是上題的朋友,那麼路就是朋友們之間的關係,那麼兩堆朋友之間連線,其實只需要一次就可以了,同樣,三堆朋友之間要想相互認識,只需要兩個關係就可以了,所以我們很容易可以得出朋友們要想都認識,只需要把朋友堆數 - 1 就可以了。
程式碼只需要將上題的輸出結果-1即可 ,不再貼出。
POJ 2236 Wireless Network 【傳送門】
題目大意:一片廢棄的地方,ACM協會正在進行救援,可是所有電腦的通訊設施都被破壞了,被修復好的電腦的訊號傳輸距離只有d米,現在給出所有電腦的座標,然後給出指令S與O,S後跟兩個數字代表兩個電腦的編號,詢問這兩臺電腦是否能通訊,如果能則輸出SUCCESS,否則輸出FALL;O後跟一個編號,代表修好一定電腦。
解題思路:此題運用並查集,在修復好一臺電腦之後遍歷所有修好的電腦,如果兩點間距離小於d米,則連通兩個點;遇到S的時候只需要判斷是否連通即可。
1 #include<iostream> 2 #include<cmath> 3 using namespace std; 4 #define MAXN 1005 5 6 //座標 7 struct pos{ 8 int x,y; 9 int par_id; //父輩id 10 int rank; //樹的高度 11 }; 12 13 struct pos par[MAXN]; //記錄父親 14 bool isok[MAXN]; //是否被修復成功 15 16 float Distance(struct pos a,struct pos b) 17 { 18 float dis=sqrt(fabs(a.x-b.x)*fabs(a.x-b.x)+fabs(a.y-b.y)*fabs(a.y-b.y)); 19 return dis; 20 } 21 22 void init(int n) 23 { 24 for(int i=1;i<=n;i++) 25 { 26 par[i].par_id=i; 27 par[i].rank=0; 28 isok[i]=false; 29 } 30 } 31 32 //查樹根 33 int find(int x) 34 { 35 if(par[x].par_id==x) 36 return x; 37 else 38 return par[x].par_id=find(par[x].par_id); 39 } 40 41 //合併 42 void unite(int x,int y) 43 { 44 x=find(x); 45 y=find(y); 46 if(x==y) 47 return; 48 if(par[x].rank<par[y].rank) 49 par[x].par_id=y; 50 else{ 51 par[y].par_id=x; 52 if(par[x].rank==par[y].rank) 53 par[x].rank++; 54 } 55 } 56 57 //判斷x和y是不是同一個集合 58 bool same(int x,int y) 59 { 60 return find(x)==find(y); 61 } 62 63 int main() 64 { 65 int N,d; 66 char c; 67 cin >> N >> d; 68 init(N); 69 for(int i=1;i<=N;i++) 70 { 71 int x,y; 72 cin >> x >> y; 73 par[i].x=x; 74 par[i].y=y; 75 } 76 77 while(cin >> c) 78 { 79 int p,q; 80 if(c=='O') 81 { 82 cin >> p; 83 isok[p]=true; 84 for(int i=1;i<=N;i++) 85 { 86 if(isok[i]==true&&i!=p) 87 { 88 struct pos a,b; 89 a.x=par[i].x; 90 a.y=par[i].y; 91 a.par_id=par[i].par_id; 92 b.x=par[p].x; 93 b.y=par[p].y; 94 b.par_id=par[p].par_id; 95 if(Distance(a,b)<=d) 96 { 97 // cout << Distance(a,b) << endl; 98 unite(p,i); 99 } 100 101 } 102 } 103 } 104 else if(c=='S') 105 { 106 cin >> p >> q; 107 if(same(p,q)) 108 cout << "SUCCESS\n"; 109 else 110 cout << "FAIL\n"; 111 } 112 // for(int i=1;i<=N;i++) 113 // cout << par[i].par_id << " "; 114 // cout << endl; 115 } 116 return 0; 117 }View Code
POJ 1703 Find them, Catch them 【傳送門】
題目大意:有兩個幫派,警察現在抓住了N個罪犯,輸入字母A或D,後面跟著兩個罪犯的編號。如果字元是A,則代表這一次詢問:如果不是一個幫派,則輸出“In different gangs.”;是一個幫派輸出"In the same gang.";否則輸出“Not sure yet.”。如果是字元D,那就代表著後面的兩個編號不屬於一個幫派!
解題思路:需要一個標記陣列vis,用來標記兩個罪犯(a,b)不屬於同一個幫派,如果標記陣列兩個罪犯都為0則代表不確定是哪個幫派,並且讓vis[a]=b,vis[b]=a;如果vis[a]==0&&vis[b]!=0,則讓vis[b]=a,並且連通a,vis[b];如果vis[b]==0&&vis[a]!=0,則讓vis[a]=b,並且連通b,vis[a];最後一種情況是a與b的標記陣列都不為0,那麼連2019-07-21通a,vis[b],連通b,vis[a]最後判斷即可。
1 //#include<iostream> 2 //using namespace std; 3 #include<stdio.h> 4 #define MAXN 100010 5 6 int per[MAXN],rank[MAXN],vis[MAXN]; 7 8 void init(int n) 9 { 10 int i; 11 for(i=1;i<=n;i++) 12 { 13 per[i]=i;rank[i]=0;vis[i]=0; 14 } 15 16 } 17 18 int find(int x) 19 { 20 if(x==per[x]) 21 return x; 22 return per[x]=find(per[x]); 23 } 24 25 void unite(int x,int y) 26 { 27 x=find(x); 28 y=find(y); 29 if(x==y) 30 return; 31 if(rank[x]>rank[y]) 32 per[y]=x; 33 else 34 { 35 per[x]=y; 36 if(rank[x]==rank[y]) 37 rank[y]+=1; 38 } 39 } 40 41 int Same(int x,int y) 42 { 43 if(find(x)==find(y)) 44 return 1; 45 46 } 47 48 int main() 49 { 50 // ios::sync_with_stdio(false); 51 int t; 52 scanf("%d",&t); 53 // cin >> t; 54 while(t--) 55 { 56 int n,m; 57 scanf("%d%d",&n,&m); 58 // cin >> n >> m; 59 init(n); 60 while(m--) 61 { 62 int a,b; 63 char c[2]; 64 scanf("%s",c); 65 //getchar(); 66 scanf("%d%d",&a,&b); 67 //cin >> c >> a >> b; 68 //聯立 69 //printf("%c\n",c); 70 if(c[0]=='D') 71 { 72 if(vis[a]==0&&vis[b]==0) 73 { 74 vis[a]=b; 75 vis[b]=a; 76 } 77 else if(vis[a]==0) 78 { 79 vis[a]=b; 80 unite(a,vis[b]); 81 } 82 else if(vis[b]==0) 83 { 84 vis[b]=a; 85 unite(b,vis[a]); 86 } 87 else{ 88 unite(a,vis[b]); 89 unite(b,vis[a]); 90 } 91 } 92 else{ 93 if(Same(a,b)) 94 printf("In the same gang.\n"); 95 //cout << "In the same gang.\n"; 96 else if(Same(a,vis[b])) 97 //cout << "In different gangs.\n"; 98 printf("In different gangs.\n"); 99 else 100 printf("Not sure yet.\n"); 101 //cout << "Not sure yet.\n"; 102 } 103 } 104 } 105 106 return 0; 107 }View Code
&n