https://www.luogu.org/problemnew/show/P3225
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
思路还是找出割点和所有的双连通分量,然后统计每个双连通分量里的个点的个数分情况讨论。
若该连通分量里有不少于两个割点,则它是安全的,因为无论哪个割点炸了,里面的点可以通过其他的没炸的割点跑到其他的双连通分量里去。
若该连通分量里只有一个割点,那么如果这个割点炸了,则里面的点就不可能跑到其他的双连通分量里去了,所以要在这个割点里建一个出口。
若该连通分量里一个割点也没有,说明它与外界完全不连通,这时如果只建一个出口的话,那么如果这个出口炸了就GG,所以还需要另一个出口“以防万一”*(即建两个出口)
对于方案数的话,我们发现如果要建出口的话,该双连通分量里的任何一个非割点的节点都是可以的,因此用一下组合数学就可以搞定了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 500 + 5;
int head[maxn], etot;
struct edge {
int to, nxt;
} E[maxn * maxn];
void addEdge(int u, int v) {
E[++etot] = (edge){v, head[u]};
head[u] = etot;
}
int dfn[maxn], low[maxn], tot;
int rot[maxn], num[maxn], rotid;
void tarjan(int u)
{
dfn[u] = low[u] = ++tot;
for (int i = head[u]; i; i = E[i].nxt)
{
int v = E[i].to;
if (dfn[v] == 0)
{
tarjan(v);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) num[u]++;
}
else low[u] = min(low[u], dfn[v]);
}
}
struct dualnode {
int breaknum, num;
dualnode() { breaknum = num = 0; }
} dual[maxn];
int vis[maxn], doublecnt, stop, stk[maxn];
void dfs(int u) {
vis[u] = 1;
stk[++stop] = u;
for (int i = head[u]; i; i = E[i].nxt)
{
int v = E[i].to;
if(vis[v]) continue;
dfs(v);
if(low[v] >= dfn[u]) {
++doublecnt;
int w = stk[stop];
while(w != u) {
--stop;
if(num[w]) dual[doublecnt].breaknum++;
else dual[doublecnt].num++;
w = stk[stop];
}
if(num[w]) dual[doublecnt].breaknum++;
else dual[doublecnt].num++;
}
}
}
void init() {
memset(dfn, 0, sizeof(dfn));
memset(num, 0, sizeof(num));
memset(vis, 0, sizeof(vis));
memset(dual, 0, sizeof(dual));
memset(head, 0, sizeof(head));
rotid = doublecnt = stop = etot = tot = 0;
}
int main()
{
int m, a, b, cas = 1;
while (~scanf("%d", &m) && m)
{
register int i;
int n = 0;
init();
for (i = 0; i < m; i++)
{
scanf("%d%d", &a, &b);
n = max(n, max(a, b));
addEdge(a, b);
addEdge(b, a);
}
for (i = 1; i <= n; i++)
if (dfn[i] == 0) {
rot[++rotid] = i;
tarjan(i);
}
for (i = 1; i <= rotid; i++) if(num[rot[i]] != 0)
num[rot[i]]--;
for (i = 1; i <= n; i++)
if(!vis[i]) dfs(i);
ll ans = 0, sum = 1;
for (i = 1; i <= doublecnt; i++) {
int ndecnt = dual[i].num, bekcnt = dual[i].breaknum;
if(bekcnt >= 2) continue;
else if(bekcnt == 1)
ans++, sum *= ndecnt;
else if(bekcnt == 0)
ans += 2, sum *= (ndecnt * (ndecnt - 1) / 2);
}
printf("Case %d: %lld %lld\n", cas++, ans, sum);
}
}