【BZOJ】2730: [HNOI2012]矿场搭建
Description
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
Input
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。
Output
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。
Sample Input
9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0
Sample Output
Case 1: 2 4
Case 2: 4 1
Case 2: 4 1
HINT
Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。
这道题一看就是找割点了吧 首先我们可以意识如果被爆破的点处于一个点双当中并且这个点它本身不是救援点的话 那么是不会对救援产生任何影响的
因为点双是保证了任意两个点间都有至少两条路可以互相到达
那么会对答案产生影响的就只可能是割点了 所以考虑把原图的每一个点双进行缩点 那么缩完点之后的每个连通块一定是一棵树 所以我们考虑对树上的点双进行分类讨论
一.整棵树节点数大于一
1.叶子结点 考虑如果它的割点被爆破 那么它与外界就断绝了联系 所以一定要在点双内部建造一个救援站
2.非叶子结点 这个情况可以不用考虑 因为如果他的一个割点被爆破那么另外一个割点仍然能够支持他走到另一个叶子节点的救援站 所以可以不用建造
二.整棵树只有一个节点 也就是整张图就是一个点双
这个时候应该建造两个救援站 若其中一个救援站被爆破那么还有另外一个可以到达 所以需要建造两个救援站
方案数使用乘法原理即可。
代码
#include <bits/stdc++.h> using namespace std; const int N = 500 + 5; int head[N], nex[2 * N], tov[2 * N], tot, n, m, idc, num ,cas; int col, ans, vis[N], low[N], dfn[N], root, deg = 0, size; bool cut[N], ins[N]; long long plan; void Init( ) { memset(head, 0, sizeof(head)); tot = 0; memset(vis, 0, sizeof(vis)); n = 0; memset(cut, 0, sizeof(cut)); idc = 0; memset(low, 0, sizeof(low)); col = 0; memset(ins, 0, sizeof(ins)); ans = 0; memset(dfn, 0, sizeof(dfn)); plan = 1; } void add(int u, int v) { tot ++; nex[tot] = head[u]; tov[tot] = v; head[u] = tot; } void tarjan(int u, int fa) { dfn[u] = low[u] = ++ idc; ins[u] = true; for(int i = head[u];i;i = nex[i]) { int v = tov[i]; if(v == fa) continue; if(! dfn[v]) { tarjan(v, u); low[u] = min(low[u], low[v]); if(low[v] >= dfn[u]) { if(u == root) deg ++; else cut[u] = true; } } else if(ins[v]) low[u] = min(low[u], dfn[v]); } } void dfs(int u, int fa) { if(vis[u] != col) vis[u] = col; if(cut[u]) return ; size ++; for(int i = head[u];i;i = nex[i]) { int v = tov[i]; if(v == fa) continue; if(vis[v] != col && cut[v]) num ++, vis[v] = col; if(! vis[v]) dfs(v, u); } } int main( ) { while(scanf("%d", & m) && m) { Init( ); for(int i = 1;i <= m;i ++) { int u, v; scanf("%d%d", & u, & v); n = max(n, max(u, v)); add(u ,v); add(v, u); } for(int i = 1;i <= n;i ++) if(! dfn[i]) { root = i; deg = 0; tarjan(i, 0); if(deg >= 2) cut[root] = true; } for(int i = 1;i <= n;i ++) { if(! vis[i] && ! cut[i]) { col ++; size = 0; num = 0; dfs(i, 0); if(! num) ans += 2, plan *= 1ll * size * (size - 1) / 2; if(num == 1) ans ++, plan *= size; } } printf("Case %d: %d %lld\n", ++ cas, ans, plan); } }