洛谷 - P3225 - 矿场搭建 - 双连通分量

https://www.luogu.org/problem/P3225

这个东西有点绕。

最平凡的情况,整个原图只有一个点,那么它坍塌了之后就没有点了,不需要进行任何逃生。否则,当一个点坍塌之后,每个其他点的工人都要逃向逃生出口。

首先把双连通分量缩点,缩点完成之后必定是一片森林,树与树之间是乘法原理。

单独考虑一棵树。

平凡的情况,只有一个根节点,没有边。

这种情况下,假如原图中该双连通分量中有>=2个点,则这个双连通分量中要建立2个出口(其中一个出口坍塌了,都可以从另一个出口走),否则假如只有一个点,则只需要建立1个出口。

不平凡的情况,是一棵无根树。必定存在叶子,每个叶子都必须建立1个出口。这个出口必须建立在非原图割点的位置,假如原图这个双连通分量没有割点(这个双连通分量只有一个点,去掉都没没事),则不需要加。

不是叶子的情况,当其他节点坍塌,则必定可以去到至少1个叶子节点逃生,不需要建立逃生出口。当自己(且自己只有一个点)坍塌,每个子树自己跑去自己的叶子就可以了。

那么先特判掉n=1的情况。然后在原图中跑出原图的割点。然后遍历一遍原图的点,从没有被访问过的非割点进入,搜索连接的所有非割点,这些非割点都属于这个双连通分量,而搜索到的割点也属于这个双连通分量不过不需要进去。这样会漏掉两个割点之间的所有非割点和割点,不过这貌似没有问题?因为假如都有两个非割点了,答案就是0了。而且环的另一个半(或者1/3,或者1/4)也会搜索到那两个割点的,反正都是0。

假如只有一个割点,则这个是缩点后图的叶子,贡献C(非割点数量,1)。

假如有多于两个割点,则不贡献,少数的非割点和割点都无所谓了

假如没有割点,则这个双连通分量缩点之后只剩下自己一个点作为树,那么也是贡献C(非割点数量,2),(当该双连通分量有多于两个点时),或者0(当该双连通分量只有1个点时)。

各个双连通分量之间选点独立,各个树之间选点独立,全部乘法叠加。

从这道题可以得到一个常识:每个割点不一定仅属于一个双连通分量,比如一个dio型图。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAXN = 505;

int n;
vector<int> G[MAXN];

int dfn[MAXN], low[MAXN], dfncnt;
bool cut[MAXN];
int vis[MAXN], viscol;
int cntcut, cntncut;

void init() {
    for(int i = 1; i <= 500; ++i)
        G[i].clear();
    dfncnt = 0;
    memset(cut, false, sizeof(cut));
    memset(vis, false, sizeof(vis));
    memset(dfn, false, sizeof(dfn));
    memset(low, false, sizeof(low));
    viscol = 1;
}


void tarjan(int u, int p) {
    low[u] = dfn[u] = ++dfncnt;
    ll sum = 0;
    int ch = 0;
    for(auto v : G[u]) {
        if(!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(p == -1)
                ch++;
            else if(low[v] >= dfn[u])
                cut[u] = true;
        } else
            low[u] = min(low[u], dfn[v]);
    }
    if(ch >= 2)
        cut[u] = true;
}

void dfs(int u, int p) {
    //cout << "u=" << u << endl;
    vis[u] = viscol;
    ++cntncut;
    for(auto v : G[u]) {
        if(vis[v] != viscol) {
            vis[v] = viscol;
            if(cut[v])
                ++cntcut;
            else
                dfs(v, u);
        }
    }
}

int main() {
#ifdef Yinku
    freopen("Yinku.in", "r", stdin);
#endif // Yinku
    int m, kase = 1;
    while(~scanf("%d", &m)) {
        if(m == 0)
            return 0;
        init();
        n = 0;
        for(int i = 1, u, v; i <= m; ++i) {
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
            n = max(n, max(u, v));
        }
        for(int i = 1; i <= n; ++i) {
            if(!dfn[i])
                tarjan(i, -1);
            /*if(cut[i])
                printf("cut=%d\n",i);*/
        }
        unsigned long long sum1 = 0;
        unsigned long long sum2 = 1;
        for(int i = 1; i <= n; ++i) {
            if(!vis[i]  && !cut[i]) {
                //cout<<"viscol="<<viscol<<endl;
                cntcut = cntncut = 0;
                dfs(i, -1);
                if(cntcut == 0) {
                    if(cntncut >= 2) {
                        sum1 += 2;
                        sum2 *= cntncut * (cntncut - 1) / 2;
                    } else {
                        sum1 += 1;
                    }
                } else if(cntcut == 1) {
                    ++sum1;
                    sum2 *= cntncut;
                } else
                    ;
                viscol++;
            }
        }
        printf("Case %d: %llu %llu\n", kase++, sum1, sum2);
    }
    return 0;
}

转载于:https://www.cnblogs.com/Yinku/p/11363294.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值