1. 程式人生 > 實用技巧 >[HNOI2012]礦場搭建 割點

[HNOI2012]礦場搭建 割點

題目

[HNOI2012]礦場搭建(https://ac.nowcoder.com/acm/problem/20099)

題目描述

煤礦工地可以看成是由隧道連線挖煤點組成的無向圖。為安全起見,希望在工地發生事故時所有挖煤點的工人都能有一條出路逃到救援出口處。
於是礦主決定在某些挖煤點設立救援出口,使得無論哪一個挖煤點坍塌之後,其他挖煤點的工人都有一條道路通向救援出口。
請寫一個程式,用來計算至少需要設定幾個救援出口,以及不同最少救援出口的設定方案總數。

輸入描述:

輸入檔案有若干組資料,每組資料的第一行是一個正整數 N(N ≤ 500),表示工地的隧道數,
接下來的N行每行是用空格隔開的兩個整數S和T,表示挖S與挖煤點T由隧道直接連線。
輸入資料以 0 結尾。

輸出描述:

輸入檔案中有多少組資料,輸出檔案 output.txt 中就有多少行。
每行對應一組輸入資料的結果。
其中第 i 行以 Case i: 開始(注意大小寫,Case 與 i 之間有空格,i 與:之間無空格,: 之後有空格),其後是用空格隔開的兩個正整數,
第一個正整數表示對於第 i 組輸入資料至少需 要設定幾個救援出口,
第二個正整數表示對於第 i 組輸入資料不同最少救援出口的設定方案總數。
輸入資料保證答案小於 2^64。輸出格式參照以下輸入輸出樣例。

輸入

9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0

輸出

Case 1: 2 4
Case 2: 4 1

思路

對於任何一個聯通塊,如果坍塌的是一個聯通塊中的割點的話,那麼分割成的每個小聯通塊中必須保證各有一個出口。

我們考慮所有的割點將原圖分割成若干個小聯通塊接下來分類討論:

(1)小塊不與任何一個割點相連,那我們需要在這裡設立兩個出口,以保證任何一個出口坍塌後,還有一個出口可用。

(2)小塊只與一個割點相連,那麼我們只需要設立一個即可,出口沒坍塌或割點坍塌了就直接用,如果出口坍塌了,那則可以通過沒坍塌的割點去另一個小塊的出口。

(3)小塊與超過一個割點相連,也就意味著,無論小塊中的那個點塌了,都可以通過其他的點到另一個小塊的出口。

#include<bits/stdc++.h>
#define LL long long
using namespace std;

struct Egde {
    int from, to, nxt;
} e[100005];
int head[505], cut=0, T=0;
int vis[505];
struct Edgescc {
    int dfn[505], low[505], pre[505];
    void init() {
        memset(dfn, 0, sizeof(dfn));
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        memset(pre, -1, sizeof(pre));
        cut=T=0;
    }
    void Addedge(int x, int y) {
        e[cut]= {x, y, head[x]};
        head[x]=cut++;
    }
    void TA(int u, int fa, int q) {
        dfn[u]=low[u]=++T;
        int sz=0;
        for(int i=head[u]; i!=-1; i=e[i].nxt) {
            int to=e[i].to;
            if(i==(q^1)) {
                continue;
            }
            if(!dfn[to]) {
                sz++;
                pre[to]=u;
                TA(to, u, i);
                low[u]=min(low[u], low[to]);
                if(pre[u]==-1&&sz>1&&!vis[u]) {
                    vis[u]=1;//割點
                }
                if(pre[u]!=-1&&low[to]>=dfn[u]&&!vis[u]) {
                    vis[u]=1;//割點
                }
            }
            else{
                low[u]=min(low[u], dfn[to]);
            }
        }
    }

} sc;

int sz=0, siz=0;
unordered_map<int, int> mp;
void get(int u){
    vis[u]=-1;
    siz++;
    for(int i=head[u]; i!=-1; i=e[i].nxt){
        int to=e[i].to;
        if(vis[to]==1){
            mp[to]=1;
        }
        if(vis[to]==0){
            get(to);
        }
    }
}

int main() {
    int n, m, cas=1;
    while(scanf("%d", &m), m) {
        sc.init();
        n=0;
        for(int i=1; i<=m; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            n=max(n, max(x, y));
            sc.Addedge(x, y);
            sc.Addedge(y, x);
        }
        LL ans1=0, ans2=1;
        for(int i=1; i<=n; i++){
            if(!sc.dfn[i]){
                sc.TA(i, -1, -1);
            }
        }
        for(int i=1; i<=n; i++){
            if(vis[i]==0){
                sz=siz=0;
                mp.clear();
                get(i);
                sz=mp.size();//這個連通塊和多少個割點連線
                if(sz==0){
                    if(siz>1){
                        ans2*=((1ll*siz)*(siz-1)/2);
                        ans1+=2;
                    }
                    else{
                        ans1++;
                    }
                }
                if(sz==1){
                    ans1++;
                    ans2*=siz;
                }
            }
        }
        printf("Case %d: %lld %lld\n", cas++, ans1, ans2);
    }

    return 0;
}