bzoj2730: [HNOI2012]矿场搭建(割点)
题目描述:煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入格式:输入文件有若干组数据,每组数据的第一行是一个正整数 n(n≤500),表示工地的隧道数,接下来的 n 行每行是用空格隔开的两个整数 S 和 T,表示挖煤点S与挖煤点T由隧道直接连接。输入数据以0结尾。
输出格式:包含多组数据,每行对应一个输入数据的结果,每个结果以Case i: 开始,每个输出包含两个结果,表示最少需要几个救援出口和方案数,答案保证小于\(2^{64}\)。
输入样例:
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
输出样例:
Case 1: 2 4
Case 2: 4 1
解析:似乎求一下割点就完事了。
先用tarjan求出所有的割点,再扫描每个联通块。
对于每个联通块,找出这个联通块中的割点数目。
没有割点,则要设立两个救援出口;有一个割点,设立一个救援出口即可;有两个以上的割点,那么什么都不用做,因为可以到达别的联通块。
方案的统计可以在每次求完联通块时计算即可。
代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn = 505;
int n, cut[maxn], dfn[maxn], low[maxn], num, res, ans[maxn], tot, k, vis[maxn], mx, group;
int hed[maxn * 2], nxt[maxn * 2], cnt, to[maxn * 2];
ll ansx, ansy;
int read(void) {
char c; while (c = getchar(), c < '0' || c > '9'); int x = c - '0';
while (c = getchar(), c >= '0' && c <= '9') x = x * 10 + c - '0'; return x;
}
void add(int x, int y) {
nxt[++ cnt] = hed[x]; hed[x] = cnt; to[cnt] = y;
}
void tarjan(int u, int root) {
dfn[u] = low[u] = ++ num;
int son = 0;
for (int i = hed[u]; i ; i = nxt[i]) {
int v = to[i];
if (!dfn[v]) {
son ++;
tarjan(v, root);
low[u] = min(low[u], low[v]);
if (u != root && low[v] >= dfn[u]) cut[u] = 1;
}
else low[u] = min(low[u], dfn[v]);
}
if (root == u && son > 1) cut[u] = 1;
}
void dfs(int u) { //dfs每个联通块
vis[u] = group;
res ++; //非割点数++
for (int i = hed[u]; i ; i = nxt[i]) {
int v = to[i];
if (vis[v] != group && cut[v]) tot ++, vis[v] = group; //如果这个联通块没有扫到这个点,就将割点数++
if (!vis[v]) dfs(v); //没被遍历过就继续遍历
}
}
void init(void) { //初始化
k ++; mx = 0; cnt = 0;
num = 0; res = 0; group = 0; tot = 0; ansx = 0; ansy = 1;
memset(hed, 0, sizeof(hed));
memset(nxt, 0, sizeof(nxt));
memset(to, 0, sizeof(to));
memset(vis, 0, sizeof(vis));
memset(cut, 0, sizeof(cut));
memset(dfn, 0, sizeof(dfn));
}
int main() {
while (n = read(), n > 0) {
init();
for (int i = 1; i <= n; ++ i) {
int x = read(), y = read();
mx = max(mx, x); mx = max(mx, y);
add(x, y); add(y, x);
}
for (int i = 1; i <= mx; ++ i) //tarjan求割点
if (!dfn[i]) tarjan(i, i);
for (int i = 1; i <= mx; ++ i) {
if (!vis[i] && !cut[i]) {
group ++;
res = tot = 0; //res:非割点数,tot:割点数
dfs(i);
if (!tot) ansx += 2, ansy *= res * (res - 1) >> 1;
else if (tot == 1) ansx ++, ansy *= res;
}
}
printf("Case %d: %lld %lld\n", k, ansx, ansy);
}
return 0;
}