並查集的介紹及簡單應用---藍橋杯真題:合根植物

先簡單介紹一下並查集:

在一些有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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章