[kuangbin帶你飛]專題五 並查集 題解

一葉之修發表於2019-03-31

 專題五 並查集

 

 

POJ 2236 Wireless Network

 

 

雞山村發生了一次地震。ACM (Asia Cooperated Medical 亞洲聯合醫療隊) 已經為聖維爾供電中心的電腦搭建了一個無線網路,但受到了一次不可預知的餘震攻擊,因此網路中的所有電腦都被破壞了。電腦被逐臺修復,網路逐步恢復了工作。由於受到硬體的約束,每臺電腦只能與距離它不超過 d 米的其它電腦直接通訊。但每臺電腦可被看作其它兩臺電腦的通訊中轉點,也就是說,如果電腦 A 和電腦 B 可以直接通訊,或存在一臺電腦 C 既可與 A 也可與 B 通訊,那麼電腦 A 和電腦 B 之間就能夠通訊。 

在處理網路修復的過程中,工作人員們在任何一個時刻,可以執行兩種操作:維修一臺電腦,或測試兩臺電腦是否能夠通訊。請您找出全部的測試操作。 

輸入

第一行包含了兩個整數 N 和 d (1 <= N <= 1001, 0 <= d <= 20000)。此處 N 是電腦的數目,編號從 1 到 N;同時,D 是兩臺電腦之間能夠直接通訊的最大距離。接下來的 N 行,每行包含兩個整數 xi, yi (0 <= xi, yi <= 10000),表示 N 臺電腦的座標。從第 (N+1) 行到輸入結束,是逐一執行的操作,每行包含一個操作,格式是以下兩者之一: 
1. "O p" (1 <= p <= N),表示維護電腦 p 。 
2. "S p q" (1 <= p, q <= N),表示測試電腦 p 和 q 是否能夠通訊。 

輸入不超過 300000 行。 

輸出

對於每個測試操作,如果兩臺電腦能夠通訊,則列印 "SUCCESS";否則,列印 "FAIL"。

示例輸入

4 1
0 1
0 2
0 3
0 4
O 1
O 2
O 4
S 1 4
O 3
S 1 4

示例輸出

FAIL
SUCCESS

挺簡單的一道並查集,每一次修復和已修列表中的點繼續一次有條件的合併,查詢更簡單O(1)

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;

int n,d,x,y;
char op;
int f[1005];
int w[1005][2];
vector<int> v;//鑽入啟用過的點

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

    return f[x] = find(f[x]);
}


void merge(int x,int y){
    int a = find(x);
    int b = find(y);
    if(a!=b)
        f[a] = b;
}


int main(){
    scanf("%d%d",&n,&d);

    for(int i=1;i<=n;i++){
        scanf("%d%d",&w[i][0],&w[i][1]);
        f[i] = i;
    }

    while(cin>>op){
        if(op=='O'){
            scanf("%d",&x);
            for(int i=0;i<v.size();i++){
                if((w[v[i]][0]-w[x][0])*(w[v[i]][0]-w[x][0])+(w[v[i]][1]-w[x][1])*(w[v[i]][1]-w[x][1])<=d*d)
                    merge(v[i],x);
            }
            v.push_back(x);
        }else if(op=='S'){
            scanf("%d%d",&x,&y);
            if(find(x)==find(y))
                printf("SUCCESS\n");
            else
                printf("FAIL\n");

        }
    }




	return 0;
}

 

 

 

POJ 1611 The Suspects

 

嚴重急性呼吸系統綜合症( SARS), 一種原因不明的非典型性肺炎,從2003年3月中旬開始被認為是全球威脅。為了減少傳播給別人的機會, 最好的策略是隔離可能的患者。

在Not-Spreading-Your-Sickness大學( NSYSU), 有許多學生團體。同一組的學生經常彼此相通,一個學生可以同時加入幾個小組。為了防止非典的傳播,NSYSU收集了所有學生團體的成員名單。他們的標準操作程式(SOP)如下:

一旦一組中有一個可能的患者, 組內的所有成員就都是可能的患者。

然而,他們發現當一個學生被確認為可能的患者後不容易識別所有可能的患者。你的工作是編寫一個程式, 發現所有可能的患者。

輸入

輸入檔案包含多組資料。

對於每組測試資料:

第一行為兩個整數n和m, 其中n是學生的數量, m是團體的數量。0 < n <= 30000,0 <= m <=500。

每個學生編號是一個0到n-1之間的整數,一開始只有0號學生被視為可能的患者。

緊隨其後的是團體的成員列表,每組一行。

每一行有一個整數k,代表成員數量。之後,有k個整數代表這個群體的學生。一行中的所有整數由至少一個空格隔開。

n = m = 0表示輸入結束,不需要處理。

產量

對於每種情況,輸出一行中的嫌疑人數量。

樣本輸入

100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0

樣本輸出

4
1
1

其實能想出這題用並查集,挺簡單的,把每一個組合並,若一個成員同時在2個組裡面,則這2個組也會自動合併,最後掃描一下0所在的祖先有多少個點

#include<iostream>
#include<cstdio>
using namespace std;

int n,m,k,x1,x2;
int f[30005];


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

void merge(int x,int y){
    int a = find(x);
    int b = find(y);
    if(a!=b)
        f[a] = b;
}





int main(){

    while(~scanf("%d%d",&n,&m),n+m){
        for(int i=0;i<=n;i++)
            f[i] = i;

        while(m--){
            scanf("%d%d",&k,&x1);
            for(int i=1;i<k;i++){
                scanf("%d",&x2);
                merge(x1,x2);
            }
        }

        int ans=0;
        for(int i=0;i<n;i++)
            if(find(i)==find(0))
                ans++;
        printf("%d\n",ans);
    }


	return 0;
}

 

 

HDU 1213 How Many Tables

 

問題描述

今天是伊格納修斯的生日。他邀請了很多朋友。現在是晚餐時間。伊格納修斯想知道他至少需要多少張桌子。你必須注意到並非所有的朋友都相互認識,並且所有的朋友都不想和陌生人呆在一起。

這個問題的一個重要規則是,如果我告訴你A知道B,B知道C,那意味著A,B,C彼此瞭解,所以他們可以留在一個表中。

例如:如果我告訴你A知道B,B知道C,D知道E,所以A,B,C可以留在一個表中,D,E必須留在另一個表中。所以Ignatius至少需要2張桌子。

輸入

輸入以整數T(1 <= T <= 25)開始,表示測試用例的數量。然後是T測試案例。每個測試用例以兩個整數N和M開始(1 <= N,M <= 1000)。N表示朋友的數量,朋友從1到N標記。然後M行跟隨。每一行由兩個整數A和B(A!= B)組成,這意味著朋友A和朋友B彼此瞭解。兩個案例之間會有一個空白行。

產量

對於每個測試用例,只輸出Ignatius至少需要多少個表。不要列印任何空白。

樣本輸入

2

5 3

1 2

2 3

4 5

 

5 1

2 5

樣本輸出

2

4

 

水題一道,反向思考如果大家都不認識那不就有n個桌子了嗎,每一次祖先的成功合併,答案就減一

#include<iostream>
#include<cstdio>
using namespace std;

int T,n,m,x,y,ans;
int f[1005];

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

void merge(int x,int y){
    int a = find(x);
    int b = find(y);
    if(a!=b){
        f[a] = b;
        ans--;
    }
}


int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        ans = n;
        for(int i=1;i<=n;i++)
            f[i] = i;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            merge(x,y);
        }
        printf("%d\n",ans);
    }

	return 0;
}

 

HDU 3038 How Many Answers Are Wrong

 

問題描述

TT和FF是......朋友。呃...非常非常好的朋友-________- b 

FF是一個壞孩子,他總是求助TT與他一起玩下面的遊戲。這是一個非常單調乏味的遊戲。首先,TT應該記下一系列整數-_- !!(無聊)。


然後,FF可以從中選擇連續的子序列(例如,從第三個到第五個整數的子序列)。之後,FF將詢問TT他選擇的子序列的總和是什麼。接下來,TT將回答FF的問題。然後,FF可以重做此過程。最後,FF必須計算整個整數序列。

無聊~~無聊~~一個非常無聊的遊戲!TT根本不想玩FF。為了懲罰FF,她經常故意告訴FF錯誤的答案。

這個壞孩子不是傻子。FF檢測到一些答案不相容。當然,這些矛盾使得計算序列變得困難。

然而,TT是一個漂亮可愛的女孩。她對FF沒有心。為了節省時間,如果確實沒有邏輯錯誤,她保證答案是正確的。

更重要的是,如果FF找到錯誤的答案,他會在判斷下一個答案時忽略它。

但是會有這麼多問題,糟糕的FF無法確定當前的答案是對還是錯。所以他決定寫一個程式來幫助他解決這個問題。該計劃將收到FF的一系列問題以及FF從TT收到的答案。該計劃的目的是找出錯誤的答案數量。只有通過忽略錯誤的答案,FF才能計算整個整數序列。可憐的FF沒有時間做這項工作。現在他正在尋求你的幫助〜(為什麼要為自己麻煩~~壞男孩)

輸入

第1行:兩個整數,N和M(1 <= N <= 200000,1 <= M <= 40000)。意味著TT寫了N個整數,FF問了M個問題。

線2..M + 1:線i + 1包含三個整數:Ai,Bi和Si。手段TT回答FF,從Ai到Bi的總和是Si。保證0 <Ai <= Bi <= N. 

您可以假設子序列的任何總和都適合32位整數。

產量

帶整數的單行表示錯誤的答案數。

樣本輸入

10 5

1 10 100

7 10 28

1 3 32

4 6 41

6 6 1

樣本輸出

1

 

從這題開始,難度來了,連結:hdu3038 How Many Answers Are Wrong 擴充套件並查集

#include<iostream>
#include<cstdio>
using namespace std;
 
int n,m,ans=0;
int f[200005];
int sum[200005];
 
 
int find(int x){
    if(f[x]==x)
        return x;
    int t = f[x];
    f[x] = find(f[x]);
    sum[x]+=sum[t];
 
    return f[x];
}
 
void Union(int x,int y,int v){
    int a = find(x);
    int b = find(y);
    if(a==b){
        if(sum[x]-sum[y]!=v){//如果祖先是同一個了,就可以驗算
            ans++;
            return;
        }
    }else{
        f[a] = b;
        sum[a] = -sum[x]+sum[y]+v;
    }
 
    return;
}
 
 
int main(){
    while(~scanf("%d%d",&n,&m)){//等價於scanf("%d%d",&n,&m)!=EOF
        for(int i=0;i<=n+5;i++){//每一次都是要初始化的
            f[i] = i;
            sum[i] = 0;
        }
        ans=0;
 
        while(m--){
            int a,b,v;
            scanf("%d%d%d",&a,&b,&v);
            Union(a-1,b,v);//別忘了a-1
        }
 
        printf("%d\n",ans);
    }
 
	return 0;
}

 

 

 

POJ 1182 食物鏈

 

 

Description

動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A吃B, B吃C,C吃A。 
現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們並不知道它到底是哪一種。 
有人用兩種說法對這N個動物所構成的食物鏈關係進行描述: 
第一種說法是"1 X Y",表示X和Y是同類。 
第二種說法是"2 X Y",表示X吃Y。 
此人對N個動物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。 
1) 當前的話與前面的某些真的話衝突,就是假話; 
2) 當前的話中X或Y比N大,就是假話; 
3) 當前的話表示X吃X,就是假話。 
你的任務是根據給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數。 

Input

第一行是兩個整數N和K,以一個空格分隔。 
以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。 
若D=1,則表示X和Y是同類。 
若D=2,則表示X吃Y。

Output

只有一個整數,表示假話的數目。

Sample Input

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

Sample Output

3

Source

Noi 01

 

poj 1182 食物鏈 帶權並查集經典模板(NOI2001) 

#include<cstdio>
 
const int maxn = 50000+10;
 
int p[maxn]; //存父節點
int r[maxn];//存與父節點的關係 0 同一類,1被父節點吃,2吃父節點
 
void init(int n){ //初始化
    for(int x = 1; x <= n; x++){
        p[x] = x; //開始自己是自己的父親節點
        r[x] = 0;//開始自己就是自己的父親,每一個點均獨立
    }
}
 
int find(int x){ //找父親節點
    if(x == p[x]) return x;
 
    int t = p[x];
    p[x] = find(p[x]);
    r[x] = (r[x]+r[t])%3; //回溯由子節點與父節點的關係和父節點與根節點的關係找子節點與根節點的關係
    return p[x];
}
 
void Union(int x, int y, int d){
    int fx = find(x);
    int fy = find(y);
 
    p[fy] = fx; //合併樹 注意:被 x 吃,所以以 x 的根為父
    r[fy] = (r[x]-r[y]+3+(d-1))%3; //對應更新與父節點的關係
}
 
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    init(n);
 
    int ans = 0;
    int d, x, y;
    while(m--){
        scanf("%d%d%d", &d, &x, &y);
 
        if(x > n || y > n || (d == 2 && x == y)) ans++; //如果節點編號大於最大編號,或者自己吃自己,說謊
 
        else if(find(x) == find(y)){//如果原來有關係,也就是在同一棵樹中,那麼直接判斷是否說謊
            if(d == 1 && r[x] != r[y]) ans++; //如果 x 和 y 不屬於同一類
            if(d == 2 && (r[x]+1)%3 != r[y]) ans++; // 如果 x 沒有吃 y (注意要對應Uinon(x, y)的情況,否則一路WA到死啊!!!)
        }
        else 
            Union(x, y, d); //如果開始沒有關係,則建立關係
    }
    printf("%d\n", ans);
    return 0;
}

 

 

 

 

相關文章