这个东西有点绕。
最平凡的情况,整个原图只有一个点,那么它坍塌了之后就没有点了,不需要进行任何逃生。否则,当一个点坍塌之后,每个其他点的工人都要逃向逃生出口。
首先把双连通分量缩点,缩点完成之后必定是一片森林,树与树之间是乘法原理。
单独考虑一棵树。
平凡的情况,只有一个根节点,没有边。
这种情况下,假如原图中该双连通分量中有>=2个点,则这个双连通分量中要建立2个出口(其中一个出口坍塌了,都可以从另一个出口走),否则假如只有一个点,则只需要建立1个出口。
不平凡的情况,是一棵无根树。必定存在叶子,每个叶子都必须建立1个出口。这个出口必须建立在非原图割点的位置,假如原图这个双连通分量没有割点(这个双连通分量只有一个点,去掉都没没事),则不需要加。
不是叶子的情况,当其他节点坍塌,则必定可以去到至少1个叶子节点逃生,不需要建立逃生出口。当自己(且自己只有一个点)坍塌,每个子树自己跑去自己的叶子就可以了。
那么先特判掉n=1的情况。然后在原图中跑出原图的割点。然后遍历一遍原图的点,从没有被访问过的非割点进入,搜索连接的所有非割点,这些非割点都属于这个双连通分量,而搜索到的割点也属于这个双连通分量不过不需要进去。这样会漏掉两个割点之间的所有非割点和割点,不过这貌似没有问题?因为假如都有两个非割点了,答案就是0了。而且环的另一个半(或者1/3,或者1/4)也会搜索到那两个割点的,反正都是0。
假如只有一个割点,则这个是缩点后图的叶子,贡献C(非割点数量,1)。
假如有多于两个割点,则不贡献,少数的非割点和割点都无所谓了
假如没有割点,则这个双连通分量缩点之后只剩下自己一个点作为树,那么也是贡献C(非割点数量,2),(当该双连通分量有多于两个点时),或者0(当该双连通分量只有1个点时)。
各个双连通分量之间选点独立,各个树之间选点独立,全部乘法叠加。
从这道题可以得到一个常识:每个割点不一定仅属于一个双连通分量,比如一个dio型图。
#includeusing namespace std;typedef long long ll;const int MAXN = 505;int n;vector 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="< < = 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;}