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

Luogu P3225 [HNOI2012]礦場搭建

思路

(這個題當時去qbxt的時候hzwer講過)。這個題其實涉及到一些新的知識,叫做點雙連通分量(概念很簡單,就是在一張連通的無向圖中,對於兩個點u和v,如果無論刪去哪條邊(只能刪去一條)都不

能使它們不連通,我們就說u和v邊雙連通)。

這個題就是點雙模板+組合數學。點雙模板一會兒程式碼裡會有。組合數學是統計答案的時候用的。說得這麼玄乎,其實就是Tarjan跑出割點,然後DFS跑連通塊,計算每個連通塊中的割點數目,再分類

討論即可(組合數學統計答案)。

分類討論(對於每個連通塊):

(1)沒有割點:那至少要建立兩個出口,注意是要從任意非割點的地方選擇兩個點建立。方案數為\(ans=ans\times cnt\times (cnt-1)/2\)

(玄學的組合數學,感性理解一下)。

(2)有一個割點,那隻要在這個連通塊內任意一個非割點的地方設立一個出口即可。方案數為\(ans=ans\times cnt\)。(又是玄學的組合數學,感性理解)。

(3)如果有兩個及以上個割點,那就無需建立出口,因為無論如何都可以直接到達其他連通塊。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 505
typedef long long ll;
int n, m, rt, kid, cnt;
int dfn[MAXN], low[MAXN];
int vis[MAXN];
bool cut[MAXN],flag[MAXN];
int num, tot, res1, T, cs;
ll res2;
class node{
    public:
        int to;
        node *nxt=NULL;
} edge[MAXN << 1], *head[MAXN];
class Stack{
    private:
        int stk[MAXN], top;
    public:
        inline void Push(int x) { stk[++top] = x; return; }
        inline void Pop(void) { --top; return; }
        inline int Top(void) { return stk[top]; }
} Stk;//這裡習慣性加上了個棧,但是沒用
inline int read(void){
    int f = 1, x = 0;char ch;
    do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9');
    do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');
    return f * x;
}
inline int _min(int x,int y) { return x < y ? x : y; }
inline int _max(int x,int y) { return x > y ? x : y; }
inline void _init(void){
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(cut, 0, sizeof(cut));
    memset(vis, 0, sizeof(vis));
    cnt = tot = n = res1 = T = 0;
    res2 = 1;//有乘法,所以初始化是1
    return;
}//多組資料千萬不要忘了初始化。
inline void add_edge(int x,int y){
    ++cnt;
    edge[cnt].nxt = head[x];
    head[x] = &edge[cnt];
    head[x]->to = y;
    return;
}
void tarjan(int k,int fa){
    dfn[k] = low[k] = ++tot;
    for (node *i = head[k]; i != NULL; i = i->nxt){
        int v = i->to;
        if (!dfn[v]){
            tarjan(v, k);
            low[k] = _min(low[k], low[v]);
            if (low[v]>=dfn[k]){
                if(k==rt) ++kid;
                else cut[k] = 1;
            }
        }
        else if(v!=fa) low[k] = _min(low[k], dfn[v]);
    }
    return;
}//tarjan求割點
void dfs(int x){
    vis[x] = T;//為了省去每次memset的時間耗費,這裡使用時間戳
    if(cut[x]) return;
    ++cnt;
    for (node *i = head[x]; i != NULL; i = i->nxt){
        int v = i->to;
        if(cut[v]&&vis[v]!=T)
            ++num, vis[v] = T;
        if(!vis[v]) dfs(v);
    }
}//DFS找連通塊
int main(){
    m=read();
    while (m){
        _init();//初始化
        for (int i = 1; i <= m; i++){
            int u = read(), v = read();
            n = _max(n, _max(u, v));//別忘了這個
            add_edge(u, v), add_edge(v, u);
        }
        for (int i = 1; i <= n; i++){
            if (!dfn[i]) tarjan(rt = i,0);
            if (kid>=2) cut[rt] = 1;//別忘了判斷在搜尋樹中兒子數量>=2的情況,這裡寫在外面了
            kid = 0;//別忘了清空
        }
        for (int i = 1; i <= n; i++){
            if (!vis[i]&&!cut[i]){
                ++T, cnt = num = 0;//清空,時間戳++
                dfs(i);
                if (!num) res1 += 2, res2 *= cnt * (cnt - 1) / 2;//分類討論1 
                if (num==1) ++res1, res2 *= cnt;//分類討論2
            }
        }
        printf("Case %d: %d %lld\n",++cs,res1,res2);
        m=read();
    }
    return 0;
}