PAT甲級 並查集 相關題_C++題解

並查集

PAT (Advanced Level) Practice 並查集 相關題ios

  • 《算法筆記》 重點摘要
  • 1034 Head of a Gang (30)
  • 1107 Social Clusters (30)
  • 1118 Birds in Forest (25)

《算法筆記》 9.6 並查集 重點摘要

1. 定義

  • father[i] 表示元素 i的父結點
  • father[i] = i 表示元素 i 爲該集合根結點
  • 每一個集合只存在一個根結點,且其做爲所屬集合的標識
int father[N];

2. 初始化

最初每一個元素都是一個獨立的集合算法

for (int i = 0; i <= n; i++)
    father[i] = i;

3. 查找

對給定結點尋找其根結點:反覆尋找父結點直到根結點數組

  • 遞推實現
int findFather (int x){
    while (x != father[x])
        x = father[x];
    return x;
}
  • 遞歸實現
int findFather (int x){
    if (x == father[x]) return x;
    else return findFather(father[x]);
}

4. 合併

  • 判斷兩元素是否在同一集合
    • 分別查找兩根結點
    • 判斷根結點是否相同
  • 合併集合:其中一個根結點的父節點指向另外一個根結點
  • 性質:只對不一樣集合進行合併,保證同一個集合中不會產生環,即並查集每一個集合都是一棵樹
void Union (int a, int b){
    int faA = findFather(a);
    int faB = findFather(b);
    if (faA != faB) father[faA] = faB;
}

5. 路徑壓縮

將當前結點查詢路徑上全部結點的父節點都指向根節點函數

  • 遞推實現
int findFather (int x){
    int y = x;  // 將初始查詢結點記錄下來
    while (x != father[x])  // 用 x 進行回溯
        x = father[x];   // 找到 x 對應根結點記錄在 x 裏
    while (y != father[y]){ // 用記錄的初始查詢結點從新回溯
        int z = y;  //將此結點記錄下來
        y = father[y];  //向父結點回溯
        father[z] = x;  //將當前結點指向根結點
    }
    return x;
}
  • 遞歸實現
int findFather (int x){
    if (x == father[x]) return x;
    else{
        int F  = findFather(father[x]);
        father[x] = F;
        return F;
    }
}

1034 Head of a Gang (30)

題目思路

  • 未做哈希函數,直接用 map<string,string> 存儲父子關係,用 map<string,int> 存儲結點對應參數
  • 輸入時將通話時長記錄在我的對應的權重 map 中
  • 若此人未出現過 (father.find(name1) == father.end()),將獨立成一個新集合,將其加入 map 並進行初始化 father[name1] = name1
  • 若已經出現過,沒必要初始化,直接與另外一個通話對象 Union 起來
  • 在 Union 函數中,首先查找兩我的對應的頭目
  • 每加入一個新人就會調用 Union 函數,也就會查找其頭目
  • 查找頭目時,要保證頭目是這個集合中權重最高的
  • 那麼每次找到其頭目後,要比較頭目與新人的權值,若新人權值大,應設置爲新的頭目
  • 這樣每次 Union 獲得的都是兩我的對應團伙中權值最大的頭目,將權值更大的做爲新的頭目
  • 邊輸入邊處理因而輸入完成後可找到每一個人對應的頭目,遍歷一次將團伙總通話時長和總人數分別與頭目對應存儲起來
  • 最後按標準篩選出符合要求的團伙,放到 map 中
  • 先輸出符合要求的團伙數量即對應 map.size() 再按要求輸出團伙頭目和團伙人數
#include<iostream>
#include<map>
using namespace std;
map<string,string> father;
map<string,int> weight, totaltime, membernum, head_num;
string findFather(string s){
    string t = s;
    while (father[s] != s) s = father[s];
    if (weight[s] < weight[t]){
        father[s] = t;
        father[t] = t;
        return t;
    }
    return s;
}
void Union(string s1, string s2){
    string fa1 = findFather(s1);
    string fa2 = findFather(s2);
    if (weight[fa1] > weight[fa2]) father[fa2] = fa1;
    else father[fa1] = fa2;
}
int main()
{
    int n, k, time;
    scanf("%d%d", &n, &k);
    string name1, name2;
    for (int i = 0; i < n; i++){
        cin >> name1 >> name2 >> time;
        weight[name1] += time;
        weight[name2] += time;
        if (father.find(name1) == father.end()) father[name1] = name1;
        if (father.find(name2) == father.end()) father[name2] = name2;
        Union(name1, name2);
    }
    for (auto it: father){
        totaltime[findFather(it.first)] += weight[it.first];
        membernum[findFather(it.first)]++;
    }
    for (auto it: membernum)
        if (it.second > 2 && totaltime[it.first] / 2 > k) head_num[it.first] = it.second;
    cout << head_num.size() << endl;
    for (auto it: head_num) cout << it.first << " " << it.second << endl;
    return 0;
}

簡化前 findFather 函數

string findFather(string s){
    while (father[s] != s){
        string fa = father[s];
        if (weight[fa] < weight[s]){
            if (father[fa] == fa) father[s] = s;
            else father[s] = father[fa];
            father[fa] = s;
        }
        else s = father[s];
    }
    return s;
}

1107 Social Clusters (30)

題目思路

  • 用 set 數組保存每一個人的興趣愛好
  • 輸入完成後遍歷 set 數組,將每一個與以前的人的愛好 set 有重合的進行合併
  • 合併處理完成後,遍歷 father 數組,記錄根結點即集合數,給每一個結點對應根結點記錄的集合人數 ++
  • 將 map 按值排序,成對放入 vector,寫 cmp 函數實現,最後按要求輸出
  • 注意:合併時,要對此人以前全部人的興趣集合進行檢查,不能檢查到有相同的就停下,有三個點過不去。spa

    問題在於若是前面有一我的興趣爲{3},一我的興趣爲{5},如今這我的興趣爲{3,5},在處理這我的以前,前兩我的並不會被放入同一個 cluster 中,只有將如今這我的與前兩我的分別合併,他們三人才能進入同一個 clusterrest

#include<iostream>
#include<set>
#include<algorithm>
#include<vector>
#include<unordered_map>
using namespace std;
int father[1001];
set<int> hobbies[1001];
bool cmp (pair<int,int> a, pair<int,int> b){ return a.second > b.second; }
int findFather(int x){
    while (x != father[x]) x = father[x];
    return x;
}
void Union(int a, int b){
    int faA = findFather(a);
    int faB = findFather(b);
    if (faA != faB) father[faA] = faB;
}
int main()
{
    for (int i = 0; i < 1001; i++) father[i] = i;
    int n, k, hobby, clusternum = 0;
    scanf("%d", &n);
    for (int i = 0; i < n; i++){
        scanf("%d: ", &k);
        for (int j = 0; j < k; j++){
            scanf("%d", &hobby);
            hobbies[i].insert(hobby);
        }
    }
    for (int i = 1; i < n; i++)
        for (int j = 0; j < i; j++)
            for (auto it: hobbies[i])
                if (hobbies[j].find(it) != hobbies[j].end()) Union(i,j);
    unordered_map<int,int> peoplenum;
    for (int i = 0; i < n; i++){
        if (father[i] == i) clusternum++;
        peoplenum[findFather(i)]++;
    }
    vector<pair<int,int>> sortnum;
    for (auto it: peoplenum) sortnum.push_back({it.first,it.second});
    sort(sortnum.begin(),sortnum.end(),cmp);
    printf("%d\n%d",clusternum,sortnum[0].second);
    for (int i = 1; i < sortnum.size(); i++) printf(" %d",sortnum[i].second);
    return 0;
}

1118 Birds in Forest (25)

題目思路

  • 鳥編號從 1 開始,father 數組大小要開到 10001,並查集初始化 father[i] = i
  • 每輸入一幅畫中鳥的數量,先接收輸入第一隻鳥的編號,再循環接收其餘的鳥,將後輸入的鳥與第一隻鳥合併
  • 用 set 記錄鳥的數量,每輸入一隻鳥就壓入 set,set 會自動去重,輸入完成後 set 的大小即爲鳥的數量
  • 因爲鳥編號連續,由 set 大小便可知 father 數組記錄到了哪裏
  • 遍歷記錄了鳥信息的部分 father 數組,找到 father[i+1] == i+1 的即爲根節點,根結點樹即爲集合數也即爲樹的棵數
  • 最後看兩隻鳥是否在同一棵樹,分別查詢兩隻鳥的根結點,比較是否相等便可
#include<iostream>
#include<set>
using namespace std;
int father[10001];
int findFather(int x){
    while (x != father[x]) x = father[x];
    return x;
}
void Union(int a, int b){
    int faA = findFather(a);
    int faB = findFather(b);
    if (faA != faB) father[faA] = faB;
}
int main()
{
    for (int i = 0; i < 10001; i++) father[i] = i;
    int n, k, q, q1, q2, fbird, bird, treenum = 0;
    scanf("%d", &n);
    set<int> birds;
    for (int i = 0; i < n; i++){
        scanf("%d%d", &k, &fbird);
        birds.insert(fbird);
        for (int j = 0; j < k - 1; j++){
            scanf("%d", &bird);
            birds.insert(bird);
            Union(fbird,bird);
        }
    }
    for (int i = 0; i < birds.size(); i++)
        if (father[i+1] == i+1) treenum++;
    printf("%d %d\n", treenum, birds.size());
    scanf("%d", &q);
    for (int i = 0; i < q; i++){
        scanf("%d%d", &q1, &q2);
        printf("%s\n", findFather(q1) == findFather(q2) ? "Yes" : "No");
    }
    return 0;
}
相關文章
相關標籤/搜索