1. 程式人生 > 其它 >並查集演算法ヽ(=^・ω・^=)丿

並查集演算法ヽ(=^・ω・^=)丿

技術標籤:演算法入門演算法

並查集演算法ヽ(=・ω・=)丿

一、前言

​ 這星期在翻題單,偶然看到了並查集,我之前新生賽被這個東西卡了一手┭┮﹏┭┮,所以就很想弄明白,所以這周就是並查集了。然後並查集可能相對於新手來說不是特別常見的概念,所以這裡大概解釋一下

二、什麼是並查集 一 一+

​ 並查集,在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查詢一個元素在哪個集合中。這一類問題近幾年來反覆出現在資訊學的國際國內賽題中,其特點是看似並不複雜,但資料量極大,若用正常的資料結構來描述的話,往往在空間上過大,計算機無法承受;即使在空間上勉強通過,執行的

時間複雜度也極高,根本就不可能在比賽規定的執行時間(1~3秒)內計算出試題需要的結果,只能用並查集來描述。

​ 這是百度的解釋,有點抽象噢X﹏X大概就是說讓一些元素有一個共同的老大,然後我們查詢的時候,找到他們的老大就行了。

​ 為啥要共同的老大?因為路徑壓縮,如果元素之間關係比較複雜的話,查詢的效率會大大下降(時間複雜度從O(logn)到 O(n))

核心程式碼

int find(int k){
    if(f[k]==k)return k;
    return f[k]=find(f[k]);
}

​ 看起來還是很簡單的哈,讓我們一起刷一些題看一看

三、go go go

1、並查集

題目描述

如題,現在有一個並查集,你需要完成合並和查詢操作。

輸入格式

第一行包含兩個整數 N*,M,表示共有 N 個元素和 MM 個操作。

接下來 M行,每行包含三個整數 Xi,Yi,Zi

當 Zi=1 時,將 Xi與 Yi 所在的集合合併。

當 Zi=2 時,輸出 Xi,Yi 是否在同一集合內,是的輸出 Y ;否則輸出 N

輸出格式

對於每一個 Zi = 2 的操作,都有一行輸出,每行包含一個大寫字母,為 Y 或者 N

輸入輸出樣例

輸入 #1複製

4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

輸出 #1複製

N
Y
N
Y

說明/提示

對於 30% 的資料,N ≤10,M ≤20 。

對於 70% 的資料,N ≤100,1 M≤10^3 。

對於 100% 的資料,1≤N≤104,1≤*M*≤2×105 。

這題完全就是並查集的模板,按照剛剛說的程式碼直接套即可,相當於大家一切先上個手。

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
int fa[10009];
int find(int k){
    if(fa[k] == k){
        return fa[k];
    }
    else{
        return fa[k] = find(fa[k]);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int n,m;
    cin >> n>> m;
    //初始化,自己是自己的老大
    for(int i = 0; i <n; i++){
        fa[i] = i;
    }
    for(int i = 0 ; i < m; i++){
        int x,y,z;
        cin >> z >> x >> y;
        if(z == 1){
            fa[find(x)] = find(y);
        }
        if(z == 2){
            if(find(x) == find(y)){
                cout<<"Y"<<endl;
            }
            else{
                cout<<"N"<<endl;
            }
        }
    }
    return 0;
}

2、親戚

題目背景

若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,現在給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。

題目描述

規定:x和y是親戚,y和z是親戚,那麼x和z也是親戚。如果x,y是親戚,那麼x的親戚都是y的親戚,y的親戚也都是x的親戚。

輸入格式

第一行:三個整數n,m,p,(n<=5000,m<=5000,p<=5000),分別表示有n個人,m個親戚關係,詢問p對親戚關係。

以下m行:每行兩個數Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有親戚關係。

接下來p行:每行兩個數Pi,Pj,詢問Pi和Pj是否具有親戚關係。

輸出格式

P行,每行一個’Yes’或’No’。表示第i個詢問的答案為“具有”或“不具有”親戚關係。

輸入輸出樣例

輸入 #1

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

輸出 #1

Yes
Yes
No

這道題是並查集的應用,其實和第一道題一樣,沒有太大難度,直接上程式碼

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
int fa[6000];
int find(int k){
    if(fa[k] == k){
        return k;
    }
    return fa[k] = find(fa[k]);
}
int main()
{
    ios::sync_with_stdio(false);
    int a,b,c;
    cin >> a >> b >> c;
    for(int i = 0 ; i < a; i++){
        fa[i] = i;
    }
    int m,n;
    for(int i = 0; i < b; i++){
        cin >> m >> n;
        fa[find(m)] = find(n);
    }
    for(int i = 0; i < c; i++){
        cin >> m >> n;
        if(find(m) == find(n)){
            cout<<"Yes"<<endl;
        }
        else{
            cout<<"No"<<endl;
        }
    }
    return 0;
}

3、乳酪

題目描述

現有一塊大乳酪,它的高度為 h,它的長度和寬度我們可以認為是無限大的,乳酪中間有許多半徑相同的球形空洞。我們可以在這塊乳酪中建立空間座標系,在座標系中,乳酪的下表面為 z=0,乳酪的上表面為 z=h

現在,乳酪的下表面有一隻小老鼠 Jerry,它知道乳酪中所有空洞的球心所在的座標。如果兩個空洞相切或是相交,則 Jerry 可以從其中一個空洞跑到另一個空洞,特別地,如果一個空洞與下表面相切或是相交,Jerry 則可以從乳酪下表面跑進空洞;如果一個空洞與上表面相切或是相交,Jerry 則可以從空洞跑到乳酪上表面。

位於乳酪下表面的 Jerry 想知道,在不破壞乳酪的情況下,能否利用已有的空洞跑 到乳酪的上表面去?

空間內兩點 P1(x1,y1,z1) P2(x2,y2,z2) 的距離公式如下:

dist(P1,P2)=[(x1−x2)2+(*y*1−*y*2)2+(z1−z2)2](1/2)

輸入格式

每個輸入檔案包含多組資料。

第一行,包含一個正整數 T,代表該輸入檔案中所含的資料組數。

接下來是 T 組資料,每組資料的格式如下: 第一行包含三個正整數 n,h,r,兩個數之間以一個空格分開,分別代表乳酪中空洞的數量,乳酪的高度和空洞的半徑。

接下來的 n 行,每行包含三個整數 x,y,z,兩個數之間以一個空格分開,表示空洞球心座標為 (x,y,z)(x,y,z)。

輸出格式

T 行,分別對應 T 組資料的答案,如果在第 i 組資料中,Jerry 能從下表面跑到上表面,則輸出 Yes,如果不能,則輸出 No

輸入輸出樣例

輸入 #1

3 
2 4 1 
0 0 1 
0 0 3 
2 5 1 
0 0 1 
0 0 4 
2 5 2 
0 0 2 
2 0 4

輸出 #1

Yes
No
Yes

說明/提示

【輸入輸出樣例 11 說明】

img

第一組資料,由乳酪的剖面圖可見:

第一個空洞在 (0,0,0) 與下表面相切;

第二個空洞在 (0,0,4) 與上表面相切;

兩個空洞在(0,0,2) 相切。

輸出 Yes

第二組資料,由乳酪的剖面圖可見:

兩個空洞既不相交也不相切。

輸出 No

第三組資料,由乳酪的剖面圖可見:

兩個空洞相交,且與上下表面相切或相交。

輸出 Yes

【資料規模與約定】

對於 20% 的資料,n=1,1≤hr≤10^4,座標的絕對值不超過 10^4。

對於 40% 的資料,1≤n≤8,1≤hr≤10^4,座標的絕對值不超過 10^4。

對於 80% 的資料,1≤n≤103,1≤*h*,*r*≤104,座標的絕對值不超過 10^4。

對於 100% 的資料,1≤n≤1×10^3,1≤h,r≤109,T≤20,座標的絕對值不超過 10^9。

相比前兩道題,這道題難度就大一些了。我當時新生賽也是卡在一道類似的題上面。但現在回過來看的話,也是有一些思路的。我們來將這道題的資訊和並查集的特點來對應的話,會發現難點主要是這道題沒告訴我們哪些元素是一個集合,所以我們要自己判斷,就是拿距離公式看哪些元素是可以放在一個集合的。還有一個難點是輸出,並查集模板給出了要輸出的兩個元素,這道題其實同樣是兩個元素,分別是接觸上面的元素和接觸下面的元素,判斷方法也是一樣的,只不過這道題需要我們對之前錄入的元素一一比對。

#include <iostream>
#include <bits/stdc++.h>
#define ll long long
#pragma GCC optimize(2)
using namespace std;
ll bottom[200000],top[200000];
ll x[200000], y[200000], z[200000];
ll fa[200000];
int find(int k){
    if(fa[k] == k){
        return k;
    }
    return fa[k] = find(fa[k]);
}
int main()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    for(int i = 0 ; i <T; i++){
        ll n,h,r,tbottom = 0,ttop = 0;
        cin >> n >> h >> r;
        //資料初始化
        for(int j = 0; j < n; j++){
            fa[j] = j;
        }
        for(int j = 0; j < n; j++){
            cin >> x[j] >> y[j] >> z[j];
            //判斷是否能觸碰底部
            if(z[j] + r >= h){
                top[ttop] = j;
                ttop++;
            }
            //判斷是否能觸碰頂部
            if(z[j] - r <= 0){
                bottom[tbottom] = j;
                tbottom++;
            }
            //構建並查集
            for(int k = 0; k < j; k++){
                if ((x[j]-x[k])*(x[j]-x[k])+(y[j]-y[k])*(y[j]-y[k]) > 4*r*r) {
                    continue;
                }
                if (((x[k]-x[j])*(x[k]-x[j])+(y[k]-y[j])*(y[k]-y[j])+(z[k] - z[j]) * (z[k] - z[j])) <= 4*r*r){
                    fa[find(k)] = find(j);
                }
            }
         }
            int p;
            for(p = 0; p < ttop; p++){
                int q;
                for(q = 0; q < tbottom; q++){
                    if(find(top[p]) == find(bottom[q])){
                        cout << "Yes"<<endl;
                        break;
                    }
                }
                if(q < tbottom){
                    break;
                }
            }
            if(p == ttop){
                cout<<"No"<<endl;
            }
    }
    return 0;
}

4、搭配購買

題目描述

明天就是母親節了,電腦組的小朋友們在忙碌的課業之餘挖空心思想著該送什麼禮物來表達自己的心意呢?聽說在某個網站上有賣雲朵的,小朋友們決定一同前往去看看這種神奇的商品,這個店裡有 nn 朵雲,雲朵已經被老闆編號為 1,2,3,…,n1,2,3,…,n,並且每朵雲都有一個價值,但是商店的老闆是個很奇怪的人,他會告訴你一些雲朵要搭配起來買才賣,也就是說買一朵雲則與這朵雲有搭配的雲都要買,電腦組的你覺得這禮物實在是太新奇了,但是你的錢是有限的,所以你肯定是想用現有的錢買到儘量多價值的雲。

輸入格式

第一行輸入三個整數,n,m,w,表示有 n 朵雲,m 個搭配和你現有的錢的數目。

第二行至 n+1 行,每行有兩個整數, ci*,*di,表示第 i 朵雲的價錢和價值。

n+2 至 n+1+m 行 ,每行有兩個整數 ui,vi。表示買第 ui 朵雲就必須買第 vi 朵雲,同理,如果買第 vi 朵就必須買第 ui 朵。

輸出格式

一行,表示可以獲得的最大價值。

輸入輸出樣例

輸入 #1

5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2

輸出 #1

1

說明/提示

  • 對於 30%30% 的資料,滿足 1≤n≤100;
  • 對於 50%50% 的資料,滿足 1≤n,w≤10^3,1≤m≤100;
  • 對於 100%100% 的資料,滿足 1≤n≤104,0≤*m*≤5×103。

這道題的並查集部分是直接給出了關係,我們直接使用並查集模板即可,關鍵是這道題還涉及到並查集的應用,我們需要統計出來每個集合的價錢和價值,然後利用動態規劃中的01揹包問題模板來解決,揹包問題有機會我們再展開來了解。

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
using namespace std;
ll m,n,w,res = 0;
ll mon[30007];
ll evl[30007];
ll count1[30007];//統計價錢
ll count2[30007];//統計價值
ll tot = 1;//統計有多少個組合
ll t[300007];//記錄每個組合的老大
ll f[300007];
ll find(int k){
    if(f[k] == k){
        return k;
    }
    return f[k] = find(f[k]);
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m >> w;
    for(int i = 1; i <= n; i++){
        f[i] = i;
    }
    for(int i = 1 ; i <= n; i++){
        cin >> mon[i] >> evl[i];
    }
    for(int i = 1 ;i <= m; i++){
        int m,n;
        cin >> m >> n;
        f[find(m)] = find(n);
    }
    t[1] = find(1);
    count1[1] = mon[1];
    count2[1] = evl[1];
    for(int i = 2 ; i <= n; i++){
        int j = 1;
        for(j = 1; j <= tot; j++){
            if(t[j] == find(i)){
                break;
            }
        }
        if(j > tot){
            tot++;
            t[tot] = find(i);
            count1[tot] = mon[i];
            count2[tot] = evl[i];
        }
        else{
            count1[j] += mon[i];
            count2[j] += evl[i];
        }
    }
    ll dp[10003] = {0};
    for(int i = 1; i <= tot; i++){
        for(int j = w; j >= count1[i]; j--)
        dp[j] = max(dp[j], dp[j - count1[i]] + count2[i]);
    }
    cout<<dp[w]<<endl;
    return 0;
}

5、朋友

題目背景

小明在A公司工作,小紅在B公司工作。

題目描述

這兩個公司的員工有一個特點:一個公司的員工都是同性。

A公司有N名員工,其中有P對朋友關係。B公司有M名員工,其中有Q對朋友關係。朋友的朋友一定還是朋友。

每對朋友關係用兩個整數(Xi,Yi)組成,表示朋友的編號分別為Xi,Yi。男人的編號是正數,女人的編號是負數。小明的編號是1,小紅的編號是-1.

大家都知道,小明和小紅是朋友,那麼,請你寫一個程式求出兩公司之間,通過小明和小紅認識的人最多一共能配成多少對情侶。(包括他們自己)

輸入格式

第1行,4個空格隔開的正整數N,M,P,Q。

之後P行,每行兩個正整數Xi,Yi。

之後Q行,每行兩個負整數Xi,Yi。

輸出格式

一行,一個正整數,表示通過小明和小紅認識的人最多一共能配成多少對情侶。(包括他們自己)

輸入輸出樣例

輸入 #1

4 3 4 2
1 1
1 2
2 3
1 3
-1 -2
-3 -3

輸出 #1

2

說明/提示

對於30%資料,N,M<=100,P,Q<=200

對於80%資料,N,M<=4000,P,Q<=10000.

對於全部資料,N,M<=10000,P,Q<=20000。

這道題我們也很容易看出來是兩個並查集,然後再搜出和1老大相同的元素個數,然後再輸出小的那個。就是負數可能不太好處理,*=-1變正數就好了

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
int f1[20000];
int f2[20000];
int m,n,p,q;
int find1(int k){
    if(f1[k] == k){
        return k;
    }
    return f1[k] = find1(f1[k]);
}
int find2(int k){
    if(f2[k] == k){
        return k;
    }
    return f2[k] = find2(f2[k]);
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> m>> n >> p >> q;
    for(int i = 1; i <= m; i++){
        f1[i] = i;
    }
    for(int i = 1 ; i <= n; i++){
        f2[i] = i;
    }
    for(int i = 1; i <= p; i++){
        int x,y;
        cin >> x >> y;
        f1[find1(x)] = find1(y);
    }
    for(int i = 1; i <= q; i++){
        int x,y;
        cin >> x >> y;
        x *= -1;
        y *= -1;
        f2[find2(x)] = find2(y);
    }
    int a1 = 0,a2 = 0;
    int t1 = find1(1);
    int t2 = find2(1);
    for(int i = 1; i <= m; i++){
        if(find1(i) == t1){
            a1++;
        }
    }
    for(int i = 1; i <= n; i++){
        if(find2(i) == t2){
            a2++;
        }
    }
    if(a1 > a2){
        cout << a2;
    }
    else{
        cout << a1;
    }
    return 0;
}

6、修復公路

題目背景

A地區在地震過後,連線所有村莊的公路都造成了損壞而無法通車。政府派人修復這些公路。

題目描述

給出A地區的村莊數N,和公路數M,公路是雙向的。並告訴你每條公路的連著哪兩個村莊,並告訴你什麼時候能修完這條公路。問最早什麼時候任意兩個村莊能夠通車,即最早什麼時候任意兩條村莊都存在至少一條修復完成的道路(可以由多條公路連成一條道路)

輸入格式

第11行兩個正整數N,M

下面M行,每行3個正整數x,y,t,告訴你這條公路連著x,y兩個村莊,在時間t時能修復完成這條公路。

輸出格式

如果全部公路修復完畢仍然存在兩個村莊無法通車,則輸出−1,否則輸出最早什麼時候任意兩個村莊能夠通車。

輸入輸出樣例

輸入 #1

4 4
1 2 6
1 3 4
1 4 5
4 2 3

輸出 #1

5

說明/提示

N≤1000,M≤100000

xN,yN,t≤100000

這道題我的想法是儘可能用較少的時間來嘗試通路,所以我們需要將資料排序,然後用貪心演算法,從時間最短的一個一個試,看什麼時候能通路,所以這裡我們還需要用到結構體,用結構體來儲存每條路的情況。

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef struct road{
    int x, y;
    int t;
}road;
road roads[100009];
bool cmp(road r1, road r2){
    return r1.t < r2.t;
}
int f[1009];
int find(int k){
    if(f[k] == k){
        return k;
    }
    else{
        return f[k] = find(f[k]);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int n,m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        f[i] = i;
    }
    for(int i = 1; i <= m; i++){
        cin >> roads[i].x >> roads[i].y >> roads[i].t;
    }
    sort(roads+1,roads+m+1,cmp);
    int i;
    for(i = 1;i <= m; i++){
        f[find(roads[i].x)] = find(roads[i].y);
        int j;
        int a = find(1);
        for(j = 1; j <= n; j++){
            if(find(j) != a){
                break;
            }
        }
        if(j > n){
            break;
        }
    }
    if(i > m){
        cout<<-1<<endl;
        return 0;
    }
    cout<<roads[i].t;
    return 0;
}

7、村村通

題目描述

某市調查城鎮交通狀況,得到現有城鎮道路統計表。表中列出了每條道路直接連通的城鎮。市政府 “村村通工程” 的目標是使全市任何兩個城鎮間都可以實現交通(但不一定有直接的道路相連,只要相互之間可達即可)。請你計算出最少還需要建設多少條道路?

輸入格式

輸入包含若干組測試測試資料,每組測試資料的第一行給出兩個用空格隔開的正整數,分別是城鎮數目 n 和道路數目 m ;隨後的 m 行對應 m 條道路,每行給出一對用空格隔開的正整數,分別是該條道路直接相連的兩個城鎮的編號。簡單起見,城鎮從 1 到 n 編號。

注意:兩個城市間可以有多條道路相通。

輸出格式

對於每組資料,對應一行一個整數。表示最少還需要建設的道路數目。

輸入輸出樣例

輸入 #1

4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0

輸出 #1

1
0
2
998

說明/提示

資料規模與約定

對於 100% 的資料,保證 1≤n<1000 。

這道題是考察並查集合並之後的集合數量,我們只需要找出老大的總數即可。

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
int f[1009];
int find(int k){
    if(f[k] == k){
        return k;
    }
    return f[k] = find(f[k]);
}
int book[1009];
int main()
{
    ios::sync_with_stdio(false);
    int n, m;
    for(;cin >> n>> m;){
        int tot = 0;
    for(int i = 1 ; i <= n; i++){
        f[i] = i;
    }
    for(int i = 0; i < m; i++){
        int x,y;
        cin >> x >> y;
        f[find(x)] = find(y);
    }
    for(int i = 1 ; i <= n; i++){
        int a = find(i);
        int j;
        for(j =  1; j <= tot; j++){
            if(book[j] == a){
                break;
            }
        }
        if(j > tot){
            tot++;
            book[tot] = a;
        }
    }
        cout<<tot - 1<<endl;;
    }
    return 0;
}

8、一中運動會之百米跑

題目背景

在一大堆秀恩愛的**之中,來不及秀恩愛的蘇大學神踏著堅定(?)的步伐走向了100米跑的起點。這時蘇大學神發現,百米賽跑的參賽同學實在是太多了,連體育老師也忙不過來。這時體育老師發現了身為體育委員的蘇大學神,便來找他幫忙。可是蘇大學神需要熱身,不然跑到一半就會抽(筋)、於是他就找到了你。。。如果你幫助體育老師解決了問題,老師就會給你5個積分。

題目描述

假設一共有N(2<=N<=20000)個參賽選手。(尼瑪全校學生都沒這麼多吧)

老師會告訴你這N個選手的名字。

接著會告訴你M(1<=M<=1000000)句話,即告訴你學生A與學生B在同一個組裡。

如果學生A與學生B在同一組裡,學生B與學生C也在同一組裡,就說明學生A與學生C在同一組。

然後老師會問你K(1<=K<=1000000)句話,即學生X和學生Y是否在同一組裡。

若是則輸出"Yes.",否則輸出"No."

輸入格式

第一行輸入N和M。

接下來N行輸入每一個同學的名字。

再往下M行每行輸入兩個名字,且保證這兩個名字都在上面的N行中出現過,表示這兩個參賽選手在同一個組裡。

再來輸入K。

接下來輸入K個體育老師的詢問。

輸出格式

對於每一個體育老師的詢問,輸出"Yes.“或"No.”。

輸入輸出樣例

輸入 #1複製

10 6
Jack
Mike
ASDA
Michel
brabrabra
HeHe
HeHE
papapa
HeY
Obama
Jack Obama
HeHe HeHE
brabrabra HeHe
Obama ASDA
papapa Obama
Obama HeHE
3
Mike Obama
HeHE Jack
papapa brabrabra

輸出 #1複製

No.
Yes.
Yes.

並查集和字串的結合,我們可以宣告一個結構體,也可以分別宣告兩個陣列來處理。

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
string name[20009];
int f[20009];
int find(int k){
    if(f[k] == k){
        return k;
    }
    return f[k] = find(f[k]);
}
int main()
{
    ios::sync_with_stdio(false);
    int N,M,K;
    cin >> N >> M;
    for(int i = 1; i <= N; i++){
        f[i] = i;
    }
    for(int i = 1; i <= N; i++){
        cin >> name[i];
    }
    for(int i = 0; i < M; i++){
        string n1,n2;
        int x,y,t = 2;
        cin >> n1 >> n2;
        for(int i = 1; i <= N; i++){
            if(name[i] == n1){
                x = i;
                t--;
            }
            if(name[i] == n2){
                y = i;
                t--;
            }
            if(t == 0){
                break;
            }
        }
        f[find(x)] = find(y);
    }
    cin >> K;
    for(int i = 1; i <= K; i++){
        string n1,n2;
        int x,y,t = 2;
        cin >> n1 >> n2;
        for(int i = 1; i <= N; i++){
            if(name[i] == n1){
                x = i;
                t--;
            }
            if(name[i] == n2){
                y = i;
                t--;
            }
            if(t == 0){
                break;
            }
        }
        if(find(x) == find(y)){
            cout<<"Yes."<<endl;
        }
        else{
            cout<<"No."<<endl;
        }
    }
    return 0;
}

9、團伙

題目描述

給定 n 個人,他們之間有兩個種關係,朋友與敵對。可以肯定的是:

  • 與我的朋友是朋友的人是我的朋友
  • 與我敵對的人有敵對關係的人是我的朋友

現在這 n 個人進行組團,兩個人在一個團隊內當且僅當他們是朋友。

求最多的團體數。

輸入格式

第一行一個整數 n 代表人數。
第二行一個整數 m 代表每個人之間的關係。
接下來 m 行每行一個字元 opt 與兩個整數 p,q

  • 如果 optF 代表 pq 為朋友。
  • 如果 optE 代表 pq 為敵人。

輸出格式

一行一個整數代表最多的團體數。

輸入輸出樣例

輸入 #1

6
4
E 1 4
F 3 5
F 4 6
E 1 2

輸出 #1

3

說明/提示

對於 100% 的資料,2≤n≤1000,1≤m≤5000,1≤p,qn

w(゚Д゚)w沒想到居然碰到了我進工作室時的筆試題,這道題朋友部分就是普通的並查集,但是敵人部分還是需要思考一下的。敵人的敵人是朋友,當一個人不止一個敵人的時候,我們就把他的敵人合併,最後檢視集合的數量,之前也遇到過類似的,難度也不大。

AC code

#include <iostream>
#include <bits/stdc++.h>
//#pragma GCC optimize(2)
using namespace std;
int f[1009];
int find(int k){
    if(f[k] == k){
        return k;
    }
    else{
        return f[k] = find(f[k]);
    }
}
int haters[1009];
int res[1009];
int main()
{
    ios::sync_with_stdio(false);
    int n,m;
    cin >> n >> m;
    for(int i = 1 ; i <= n; i++){
        f[i] = i;
    }
    for(int i = 0; i < m; i++){
        string  s;
        int x,y;
        cin >> s >> x >> y;
        if(s == "F"){
            f[find(x)] = find(y);
        }
        if(s == "E"){
            if(haters[x] == 0){
                haters[x] = find(y);
            }
            else{
                f[find(y)] = find(haters[x]);
            }
            if(haters[y] == 0){
                haters[y] = find(x);
            }
            else{
                f[find(x)] = find(haters[y]);
            }
        }
    }
    int tot = 1;
    res[1] = find(1);
    for(int i = 2; i <= n; i++){
        int j;
        int a = find(i);
        for(j = 1; j <= tot; j++){
            if(a == res[j]){
                break;
            }
        }
        if(j > tot){
            tot++;
            res[tot] = find(i);
        }
    }
    cout << tot;
    return 0;
}

10、無線通訊網

題目描述

國防部計劃用無線網路連線若干個邊防哨所。2 種不同的通訊技術用來搭建無線網路;

每個邊防哨所都要配備無線電收發器;有一些哨所還可以增配衛星電話。

任意兩個配備了一條衛星電話線路的哨所(兩邊都ᤕ有衛星電話)均可以通話,無論他們相距多遠。而只通過無線電收發器通話的哨所之間的距離不能超過 D,這是受收發器的功率限制。收發器的功率越高,通話距離 D 會更遠,但同時價格也會更貴。

收發器需要統一購買和安裝,所以全部哨所只能選擇安裝一種型號的收發器。換句話說,每一對哨所之間的通話距離都是同一個 D。你的任務是確定收發器必須的最小通話距離 D,使得每一對哨所之間至少有一條通話路徑(直接的或者間接的)。

輸入格式

從 wireless.in 中輸入資料第 1 行,2 個整數 S 和 P,S 表示可安裝的衛星電話的哨所數,P 表示邊防哨所的數量。接下里 P 行,每行兩個整數 x,y 描述一個哨所的平面座標(x, y),以 km 為單位。

輸出格式

輸出 wireless.out 中

第 1 行,1 個實數 D,表示無線電收發器的最小傳輸距離,精確到小數點後兩位。

輸入輸出樣例

輸入 #1

2 4
0 100
0 300
0 600
150 750

輸出 #1

212.13

說明/提示

對於 20% 的資料:P = 2,S = 1

對於另外 20% 的資料:P = 4,S = 2

對於 100% 的資料保證:1 ≤ S ≤ 100,S < P ≤ 500,0 ≤ x,y ≤ 10000。

這道題有點像第六題,同樣的貪心演算法,結構體,排序等等

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef struct edge{
    int u,v;//連線的兩邊
    double dis;//距離,用來排序
}edge;
edge edges[300000];
int x[600];
int y[600];
int P,S;
int f[600];
int find(int k){
    if(f[k] == k){
        return k;
    }
    else{
        return f[k] = find(f[k]);
    }
}
int t = 0;
bool cmp(edge e1, edge e2){
    return e1.dis < e2.dis;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> S >> P;
    for(int i = 1; i <= P; i++){
        f[i] = i;
    }
    for(int i = 1; i<= P; i++){
        cin >> x[i] >> y[i];
    }
    for(int i = 1; i <= P; i++){
        for(int j = 1; j <= P; j++){
            edges[t].u = i;
            edges[t].v = j;
            edges[t].dis = sqrt((x[i] - x[j])*(x[i]-x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
            t++;
        }
    }
    sort(edges,edges+t,cmp);
    int i;
    for(i = 0;  i < t; i++){
        if(P <= S){
            break;
        }
        int a = edges[i].u;
        int b = edges[i].v;
        if(find(a) != find(b)){
            f[find(a)] = find(b);
            P--;
        }
    }
    printf("%.2lf",edges[i].dis);
    return 0;
}

11、燒錄光碟

題目描述

在JSOI2005夏令營快要結束的時候,很多營員提出來要把整個夏令營期間的資料刻錄成一張光碟給大家,以便大家回去後繼續學習。組委會覺得這個主意不錯!可是組委會一時沒有足夠的空光碟,沒法保證每個人都能拿到刻錄上資料的光碟,又來不及去買了,怎麼辦呢?!

組委會把這個難題交給了LHC,LHC分析了一下所有營員的地域關係,發現有些營員是一個城市的,其實他們只需要一張就可以了,因為一個人拿到光碟後,其他人可以帶著U盤之類的東西去拷貝啊!

可是,LHC調查後發現,由於種種原因,有些營員並不是那麼的合作,他們願意某一些人到他那兒拷貝資料,當然也可能不願意讓另外一些人到他那兒拷貝資料,這與我們JSOI宣揚的團隊合作精神格格不入!!!

現在假設總共有N個營員(2<=N<=200),每個營員的編號為1~N。LHC給每個人發了一張調查表,讓每個營員填上自己願意讓哪些人到他那兒拷貝資料。當然,如果A願意把資料拷貝給B,而B又願意把資料拷貝給C,則一旦A獲得了資料,則B,C都會獲得資料。

現在,請你編寫一個程式,根據回收上來的調查表,幫助LHC計算出組委會至少要燒錄多少張光碟,才能保證所有營員回去後都能得到夏令營資料?

輸入格式

先是一個數N,接下來的N行,分別表示各個營員願意把自己獲得的資料拷貝給其他哪些營員。即輸入資料的第i+1行表示第i個營員願意把資料拷貝給那些營員的編號,以一個0結束。如果一個營員不願意拷貝資料給任何人,則相應的行只有1個0,一行中的若干數之間用一個空格隔開。

輸出格式

一個正整數,表示最少要刻錄的光碟數。

輸入輸出樣例

輸入 #1

5
2 3 4 0
4 5 0
0
0
1 0

輸出 #1

1

這道題第一反應果斷並查集ヾ(・ω・`。)但慢著,仔細看會發現,這道題的聯絡是單向的ค(TㅅT)而一般的並查集是雙向的。所以並不是那麼簡單的。所以首先我們要判斷這道題我們輸出的特點,其實就是集合的數量加上老大是自己的人數,仔細想一下就發現是這樣,所以我們要記錄每個元素是否存在老大,然後再按並查集算出集合個數,相加即可。另外,我看到題解有人提到Floyd演算法(二叉樹當中提到過),所以我就試著寫了一下,大概原理就是,算出每個點之間的距離,只要是通路就視為一個集合,其實就是合併方法不一樣,其他和並查集差不多。

AC code(並查集)

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
int f[300];
int find(int k){
    if(f[k] == k){
        return k;
    }
    return f[k] = find(f[k]);
}
int q[300];
int main()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    int tot = 0;
    for(int i = 1; i <= T; i++){
        f[i] = i;
    }
    for(int i = 1 ; i <= T; i++){
        for(;;){
            int x;
            cin >> x;
            if(x == 0){
                break;
            }
            if(x != i){
                int a = find(x);
                int b = find(i);
                q[x] = 1;
                if(a != b){
                    f[a] = b;
                }
            }
        }
    }
    for(int i = 1 ;i <= T; i++){
        if(find(i) == i || q[i] == 0){
            tot++;
        }
    }
    cout<< tot;
    return 0;
}

AC code(Floyd演算法)

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
int dis[202][202];
int f[202];
int main()
{
    ios::sync_with_stdio(false);
    int N;
    cin >>N;
    int tot = 0;
    for(int i = 1 ;i <= N; i++){
        f[i] = i;
    }
    for(int i = 1; i <= N; i++){
        for(;;){
            int x;
            cin >> x;
            if(x == 0){
                break;
            }
                dis[i][x] = 1;
        }
    }
    for(int i = 1; i <= N; i++){
        for(int k = 1; k<=N; k++){
                for(int j = 1; j<= N; j++){
                    if(dis[i][k] != 0 && dis[k][j] != 0){
                        dis[i][j] = 1;
                    }
                }

        }
    }
    for(int i = 1; i<=N; i++){
        int j = 1;
        for(j = 1; j<= N; j++){
            if(dis[i][j]){
                f[j] = f[i];
            }
        }
    }
    for(int i = 1; i <= N; i++){
        if(f[i] == i){
            tot++;
        }
    }
    cout<<tot<<endl;
    return 0;
}

12、家譜

題目背景

現代的人對於本家族血統越來越感興趣。

題目描述

給出充足的父子關係,請你編寫程式找到某個人的最早的祖先。

輸入格式

輸入由多行組成,首先是一系列有關父子關係的描述,其中每一組父子關係中父親只有一行,兒子可能有若干行,用 #name 的形式描寫一組父子關係中的父親的名字,用 +name 的形式描寫一組父子關係中的兒子的名字;接下來用 ?name 的形式表示要求該人的最早的祖先;最後用單獨的一個 $ 表示檔案結束。

輸出格式

按照輸入檔案的要求順序,求出每一個要找祖先的人的祖先,格式為:本人的名字 ++ 一個空格 ++ 祖先的名字 ++ 回車。

輸入輸出樣例

輸入 #1

#George
+Rodney
#Arthur
+Gareth
+Walter
#Gareth
+Edward
?Edward
?Walter
?Rodney
?Arthur
$

輸出 #1

Edward Arthur
Walter Arthur
Rodney George
Arthur Arthur

說明/提示

規定每個人的名字都有且只有 6 個字元,而且首字母大寫,且沒有任意兩個人的名字相同。最多可能有 10^3 組父子關係,總人數最多可能達到 5×10^4 人,家譜中的記載不超過 30 代。

還是和之前一樣,給名字編號,然後並查集。還有就是注意查重,出現過的名字都不要再定義。

AC code

#include <iostream>
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
string names[50009];
int f[50009];
int find(int k){
    if(f[k] == k){
        return k;
    }
    return f[k] = find(f[k]);
}
int main()
{
    ios::sync_with_stdio(false);
    int fa = 1;
    int tot = 0;
    for(;;){
        char op;
        cin >> op;
        if(op == '#'){
            string s;
            cin >> s;
            int i;
            for(i = 1; i <= tot; i++){
                if(names[i] == s){
                    fa = i;

                    break;
                }
            }
            if(i > tot){
                tot++;
                fa = tot;
                f[tot] = tot;
                names[tot] = s;
            }
        }
        if(op == '+'){
            int i;
            string s;
            cin >> s;
            for( i =1 ;i <= tot; i++){
                if(names[i] == s){
                    break;
                }
            }
            if(i > tot){
                tot++;
                f[tot] = find(fa);
                names[tot] = s;
            }
            else{
                f[i] = find(fa);
            }
        }
        if(op == '?'){
            string s;
            cin >> s;
            int i;
            for(i = 1; i <= tot; i++){
                if(names[i] == s){
                    break;
                }
            }
            cout<<names[i]<<" "<<names[find(i)]<<endl;
        }
        if(op == '$'){
            break;
        }
    }
    return 0;
}

四、小結

並查集演算法本身難度不大,相信大家都可能輕鬆理解,但當並查集和一些其他的概念結合的時候難度就會上升,這周的題目大多不是很難,向大家展示了並查集和其他概念的簡單結合,應該會比上週的二叉樹容易理解,掌握得也更好一些