先簡單介紹一下並查集:
在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查找一個元素在哪個集合中。這一類問題近幾年來反覆出現在信息學的國際國內賽題中,其特點是看似並不複雜,但數據量極大,若用正常的數據結構來描述的話,往往在空間上過大,計算機無法承受;即使在空間上勉強通過,運行的時間複雜度也極高,根本就不可能在比賽規定的運行時間(1~3秒)內計算出試題需要的結果,只能用並查集來描述。
並查集是一種樹型的數據結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。常常在使用中以森林來表示。
簡單來說,就是:N個元素分佈在若干個互不相交的集合中,需要進行一下三個操作:
1、合併兩個集合。
2、查詢一個元素在哪個集合裏面。
3、查詢兩個元素是否屬於同一個集合。
最典型的應用就是判斷親戚關係,給定n,一共n個人,再給定m組親戚關係,最後讓你判斷任意兩個人是否是親戚關係。若a與b是親戚關係,b與c是親戚關係,那麼很顯然a,b,c三人互爲親戚關係,應該合併。
用並查集處理問題的過程中有幾個重要的函數:
1、獲得一個結點的根結點,若兩個結點的根結點相同,那麼兩個結點就可以合併到一起。
int get_root(int a) { //求根節點
if(par[a]!=a) {
par[a]=get_root(par[a]);
}
return par[a];
}
所有結點的根結點最開始都是自己,因爲每個結點合併前都是單獨存在的。查詢這裏用了遞歸的思想,實際上就是路徑壓縮。
2、查詢兩個結點是否屬於同一個集合,只需要獲取兩個結點的根結點,若二者根結點相同,就屬於同一個集合。
bool query(int a,int b) {
return get_root(a)==get_root(b);
}
3、合併操作,若要合併兩個結點,直接令一個結點的根結點的父結點爲另一個結點的根結點即可:par[get_root(a)]=get_root(b);
void merge(int a,int b) {
par[get_root(a)]=get_root(b);
}
4、若要計算每個集合有多少人,就需要改寫merge()函數:
void merge(int a,int b) {
int p1=get_root(a);
int p2=get_root(b);
if(p1==p2) {
return ;
}else {
total[p1]+=toatl[p2];
par[p2]=p1;
}
}
下面回到藍橋杯合根植物這道題:
問題描述:
w星球的一個種植園,被分成 m * n 個小格子(東西方向m行,南北方向n列)。每個格子裏種了一株合根植物。
這種植物有個特點,它的根可能會沿着南北或東西方向伸展,從而與另一個格子的植物合成爲一體。
如果我們告訴你哪些小格子間出現了連根現象,你能說出這個園中一共有多少株合根植物嗎?
輸入格式:
第一行,兩個整數m,n,用空格分開,表示格子的行數、列數(1<m,n<1000)。
接下來一行,一個整數k,表示下面還有k行數據(0<k<100000)
接下來k行,第行兩個整數a,b,表示編號爲a的小格子和編號爲b的小格子合根了。格子的編號一行一行,從上到下,從左到右編號。比如:5* 4 的小格子,編號:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
樣例輸入:
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17
樣例輸出:
5
樣例解釋:
思路分析:就是簡單的並查集應用:先合併有關係的結點,最後輸出一個有幾個不同的集合,其實就是輸出最後還有幾個根結點。
#include<bits/stdc++.h>
using namespace std;
int par[1000*1000];
int get_root(int a) { //求根節點
if(par[a]!=a) {
par[a]=get_root(par[a]);
}
return par[a];
}
void merge(int a,int b) {
par[get_root(b)]=get_root(a);
}
int main() {
set <int> s;
int m,n,k;
int x,y;
cin>>m>>n;
cin>>k;
for(int i=0;i<m*n;i++) {
par[i]=i; //剛開始構成m*n個集合,每個元素的父節點都是自己
}
for(int i=0;i<k;i++) {
cin>>x>>y;
merge(x,y); //合併x,y,最終答案就是合併後有多少個集合,即有幾個不同的根節點
}
for(int i=0;i<m*n;i++) {
s.insert(get_root(i));
}
cout<<s.size();
return 0;
}