洛谷題單指南-集合-P1551 親戚

江城伍月發表於2024-03-20

原題連結: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個集合

洛谷題單指南-集合-P1551 親戚

合併2,3,merge(2, 3), 變成4個集合

洛谷題單指南-集合-P1551 親戚

合併1,2,merge(1, 2), 變成3個集合

洛谷題單指南-集合-P1551 親戚

在判斷1、3是否屬於同一個集合時,只需要判斷1、3所在集合的根節點是否相同

if(find(1) == find(3))

5、路徑壓縮

接上圖,如果執行merge(4, 1),變成2個集合

洛谷題單指南-集合-P1551 親戚

在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)之後:

洛谷題單指南-集合-P1551 親戚

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;
}

相關文章