原題連結:https://www.luogu.com.cn/problem/P1551
題意解讀:要判斷兩人是否是親戚,只需要看兩人是否屬於一個集合,基於所有已知的親戚關係,可以建立多個有親戚關係的集合,這個過程可以藉助並查集。
解題思路:
並查集:
1、定義
並查集是一種樹形資料結構,本質上是多棵樹,每棵樹表示一個集合,主要提供兩種操作:
- 查詢某個元素屬於哪個集合
- 將兩個元素所屬的集合合併
2、原理
並查集主要採用陣列模擬樹的雙親表示法,如對於1~5的元素建立並查集,可以定義陣列int p[6],陣列的值p[i]表示元素i的父節點
初始時,每個元素的值等於自身p[i] = i,說明每個元素的父節點都是自己,各自構成一個集合。
3、查詢
當要查詢x所屬的集合,集合的編號是x所在樹的根節點,只需要如下操作:
int find(int x)
{
while(p[x] != x ]) x = p[x];
return x;
}
通常寫成遞迴形式
int find(int x)
{
if(p[x] == x) return x;
return find(p[x]);
}
4、合併
當要將x、y所屬的集合合併,只需要如下操作:
void merge(int x, int y)
{
p[find(x)] = find(y);
}
即將x所在集合的根節點的父節點設定為y所在集合的根節點
舉例:
初始時1~5的元素形成5個集合
合併2,3,merge(2, 3), 變成4個集合
合併1,2,merge(1, 2), 變成3個集合
在判斷1、3是否屬於同一個集合時,只需要判斷1、3所在集合的根節點是否相同
if(find(1) == find(3))
5、路徑壓縮
接上圖,如果執行merge(4, 1),變成2個集合
在find(4)時,需要先找到p[4] = 1,再找到p[1] = 2,p[2] = 3,如果這個鏈路很長,每次操作就比較耗時
路徑壓縮本質上就是在一次find之後,把從4開始向上一條鏈上的節點的父節點都直接指向根節點3,這樣以後再查詢元素的集合時只需要一次查詢即可
具體來說,就是在find內將遞迴函式的返回值進行賦值:
int find(int x)
{
if(p[x] == x) return x;
return p[x] = find(p[x]);
}
這樣可以保證在find(x)時將x到根節點鏈路上所有的節點都指向根節點,如find(4)之後:
1/2/4的父節點直接指向3,後續的find操作就比較快了。
並查集的原理介紹到這裡,下面實現本題程式碼。
100分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int p[N];
int n, m, q;
int find(int x)
{
if(p[x] == x) return x;
return p[x] = find(p[x]);
}
void merge(int x, int y)
{
p[find(x)] = find(y);
}
int main()
{
cin >> n >> m >> q;
int i, j;
//初始化
for(int i = 1; i <= n; i++) p[i] = i;
while(m--)
{
cin >> i >> j;
merge(i, j);
}
while(q--)
{
cin >> i >> j;
if(find(i) == find(j)) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}