[BZOJ2730][HNOI2012]矿场搭建(割点+分类讨论)

先用Tarjan求割点。
可以想到,如果图中没有割点,那么可以在任意一个位置设置救援出口,这样看上去能合法,但是如果坍塌的点正好是救援出口就不行了。
因此,要设置 2 个救援出口,方案数为C2n
如果有割点,则在每一个点双连通分量内分类讨论:
1、当前点双连通分量内只有一个割点:只需要设置 1 个救援出口,可以放在双连通分量内除割点外的任意节点上。这样,如果该双连通分量内的除救援出口之外的一个点坍塌,那么一定可以合法。如果救援出口坍塌,则此时因为救援出口不在割点上,因此该双连通分量内的点一定会与该双连通分量之外的点连通。
2、当前点双连通分量内的割点数量大于1:不需要设置救援出口。因为可能坍塌的点只有 1 <script type="math/tex" id="MathJax-Element-12">1</script>个,因此无论哪一个割点坍塌,该双连通分量内的节点都一定与该双连通分量之外的点连通。
救援出口数就是每个双连通分量的救援出口数之和。
方案数就是每个双连通分量的方案数的乘积。
代码:

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 6e5 + 5;
int n, m, ecnt, nxt[N], adj[N], go[N], times, dfn[N], low[N], top, stk[N],
tot, orzCkuohaoYkuohaoXbaosongqinghua, ysy[N], zzq[N]; vector<int> bel[N];
bool cut[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void cyx(int rt, int u) {
    dfn[stk[++top] = u] = low[u] = ++times; int orz = 0;
    for (int e = adj[u], v; e; e = nxt[e])
        if (!dfn[v = go[e]]) {
            orz++; cyx(rt, v); low[u] = min(low[u], low[v]);
            if ((u == rt && orz > 1) || (u != rt && dfn[u] <= low[v]))
                cut[u] = 1;
            if (dfn[u] <= low[v]) {
                bel[++tot].clear();
                do {
                    bel[tot].push_back(stk[top--]);
                } while (stk[top + 1] != v);
                bel[tot].push_back(u);
            }
        }
        else low[u] = min(low[u], dfn[v]);
}
void work() {
    int i, j; n = ecnt = tot = times = top = 0;
    for (i = 1; i <= m; i++) ysy[i] = read(), zzq[i] = read(),
        n = max(n, max(ysy[i], zzq[i]));
    for (i = 1; i <= n; i++) adj[i] = dfn[i] = cut[i] = 0;
    for (i = 1; i <= m; i++) add_edge(ysy[i], zzq[i]);
    for (i = 1; i <= n; i++) if (!dfn[i]) cyx(i, i);
    int res = 0; ll ans = 1; for (i = 1; i <= tot; i++) {
        int orz = 0, pyz = bel[i].size();
        for (j = 0; j < pyz; j++) if (cut[bel[i][j]]) orz++;
        if (!orz) res += 2, ans *= 1ll * pyz * (pyz - 1) / 2;
        else if (orz == 1) res++, ans *= pyz - 1;
    }
    printf("Case %d: %d %lld\n", ++orzCkuohaoYkuohaoXbaosongqinghua, res, ans);
}
int main() {
    while (m = read()) work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值