1. 程式人生 > 實用技巧 >【並查集】B003_AW_戰爭中的城市 & 家譜(連通塊 / 字串並查集)

【並查集】B003_AW_戰爭中的城市 & 家譜(連通塊 / 字串並查集)

在戰爭中,所有城市都必須通過高速公路連線起來,這一點至關重要。
如果一個城市被敵人佔領,則從該城市/往該城市的所有高速公路都將關閉。
此時,我們必須立即判斷是否需要維修其他高速公路以保持其餘城市的連通性。
給定城市與道路分佈地圖以及一個重點關注城市列表,請你判斷,當列表中的某個城市被攻陷時,至少要維修多少條高速公路才能保持其餘城市的連通性。
例如,共有 3 座城市,由 2 條高速公路將它們連通,一條連線城市 1 和城市 2,一條連線城市 1 和城市 3。
當城市 1 被敵人佔領時,我們需要在城市 2 和城市 3 之間維修一條高速公路,以保持這兩座城市的連通。
輸入格式
第一行包含三個整數 N,M,K,分別表示城市總數,高速公路總數,重點關注城市數量。
接下來 M 行,每行包含兩個整數 a,b,表示城市 a 和城市 b 之間存在一條高速公路。
所有城市編號從 1 到 N。
最後一行,包含 K 個整數,表示重點關注的城市編號。
輸出格式


共 K 行,每行輸出一個重點關注城市被佔領時,為了保持其餘城市連通性,需要維修的最少高速公路條數。
資料範圍
2≤N≤1000,
1≤M≤N(N−1)2,
1≤a,b≤N,
1≤K≤N,
KM≤3500000,
資料保證最開始所有城市保持連通。

輸入樣例:
3 2 3
1 2
1 3
1 2 3
輸出樣例:
1
0
0

方法一:並查集統計連通塊數量

反過來想,與重點城市相連的邊必須要有,如果兩個點都不是重點城市,則兩點之間的邊可以不要(因為被炸掉的只有重點城市);

import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
	static class Solution {
        class Node {
            int u,v;
            Node(int u, int v) {
                this.u=u; this.v=v;
            }
        }
        int fa[], rk[];   
        int find(int u) {
            if (fa[u]==u) return u;
            return fa[u]=find(fa[u]);
        }
        boolean isConn(int u, int v) {
            return find(u)==find(v);
        }
        void union(int u, int v) {
            int fu=find(u), fv=find(v);
            if (fu==fv) return;
            if (rk[fu] > rk[fv]) {fa[fv]=fu; rk[fu]++; }
            else                 {fa[fu]=fv; rk[fv]++; } 
        }
	void init() {
	    Scanner sc = new Scanner(new BufferedInputStream(System.in));
            BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
            int n=sc.nextInt(),m=sc.nextInt(),k=sc.nextInt();
            Node A[]=new Node[m];
            for (int i=0; i<m; i++) {
                A[i]=new Node(sc.nextInt(), sc.nextInt());
            }
            while (k-->0) {
                fa=new int[n+5]; rk=new int[n+1];
                for (int i=1; i<=n; i++) {fa[i]=i; rk[i]=1;}
                int x=sc.nextInt(), cnt=n-1;      //兩個城市可以組成
                for (int i=0; i<m; i++) {
                    int u=A[i].u, v=A[i].v;
                    if (u!=x && v!=x) {
                        if (!isConn(u,v)) {
                            union(u,v);
                            cnt--;
                        }
                    }
                }
                System.out.println(cnt-1);
            }
        }
    }
    public static void main(final String[] args) throws IOException {  
        final Solution s = new Solution();
	s.init();
    }
}

二、家譜

輸入格式
由多行組成,首先是一系列有關父子關係的描述,其中每一組父子關係由二行組成,用#name的形式描寫一組父子關係中的父親的名字,用+name的形式描寫一組父子關係中的兒子的名字;接下來用?name的形式表示要求該人的最早的祖先;最後用單獨的一個$表示檔案結束。
規定每個人的名字都有且只有6個字元,而且首字母大寫,且沒有任意兩個人的名字相同。
輸出格式
按照輸入的要求順序,求出每一個要找祖先的人的祖先,格式:本人的名字+一個空格+祖先的名字+回車。
資料範圍
最多可能有10000組父子關係。
輸入資料不超過20010行。
資料中涉及到的人數不超過10000。

輸入樣例:
#George
+Rodney
#Arthur
+Gareth
+Walter
#Gareth
+Edward
?Edward
?Walter
?Rodney
?Arthur
$
輸出樣例:
Edward Arthur
Walter Arthur
Rodney George
Arthur Arthur
#include<bits/stdc++.h>
using namespace std;
unordered_map<string, string> fa;

string find(string& u) {
    return fa[u]==u ? u : fa[u]=find(fa[u]);
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    string s,p;
    while (true) {
        cin>>s; if (s=="$") break;
        string name=s.substr(1);
        if (s[0]=='#') {
            p=name; 
            if (fa.find(p)==fa.end()) fa[p]=p;
        }if (s[0]=='+') {
            fa[name]=p;
        }if (s[0]=='?') {
            cout << name << ' ' << find(name) << '\n';
        }
    }    
    return 0;
}

複雜度分析

  • Time\(O()\)
  • Space\(O()\)