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;
}

posted @ 2020-07-26 19:53  Shadow_hyc  阅读(118)  评论(0)    收藏  举报