洛谷传送门
BZOJ传送门
题目描述
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入输出格式
输入格式:
输入文件有若干组数据,每组数据的第一行是一个正整数 N N (),表示工地的隧道数,接下来的 N N 行每行是用空格隔开的两个整数 和 T T ,表示挖 与挖煤点 T T 由隧道直接连接。输入数据以 结尾。
输出格式:
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i i 行以 : 开始(注意大小写, Case C a s e 与 i i 之间有空格, 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 264 2 64 。输出格式参照以下输入输出样例。
输入输出样例
输入样例#1:
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:
Case 1: 2 4
Case 2: 4 1
说明
Case 1 C a s e 1 的四组解分别是 (2,4),(3,4),(4,5),(4,6) ( 2 , 4 ) , ( 3 , 4 ) , ( 4 , 5 ) , ( 4 , 6 ) ;
Case 2 C a s e 2 的一组解为 (4,5,6,7) ( 4 , 5 , 6 , 7 ) 。
解题分析
考虑先将矿洞之间的割点算出, 再考虑割点间的连通块。
- 如果一个连通块两端有两个不同的割点,我们不需要在这个连通块内建立救援出口(因为可以跑到其他连通块去)。
- 如果只有一个割点, 我们就需要建立一个救援出口(避免割点塌了跑不出去)
- 如果没有割点(即单独成环),我们就需要两个救援出口。值得注意的是,如果只有一个点, 我们是只需要一个出口的。
代码也非常简单易懂, 见下:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cctype>
#include <cmath>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 1005
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
W (!isdigit(c)) c = gc;
W (isdigit(c))
x = (x << 1) + (x << 3) + c - 48, c = gc;
}
struct Edge {int to, nex;} edge[MX << 1];
int line, t, T, num, arr, cot, siz, cnt;
bool cut[MX], cons[MX];
int dfn[MX], low[MX], head[MX], col[MX];
long long ans1, ans2;
IN void addedge(const int &from, const int &to)
{edge[++cnt] = {to, head[from]}, head[from] = cnt;}
void tarjan(const int &now, const int &root)
{
dfn[now] = low[now] = ++arr;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(!dfn[edge[i].to])
{
tarjan(edge[i].to, root);
low[now] = std::min(low[now], low[edge[i].to]);
if(dfn[now] <= low[edge[i].to] && now != root) cut[now] = true;
if(now == root) ++t;
}
else low[now] = std::min(low[now], dfn[edge[i].to]);
}
if(now == root && t >= 2) cut[now] = true;
}
void DFS(const int &now)
{
++siz; col[now] = cot;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(cut[edge[i].to])
{
if(col[edge[i].to] == cot) continue;
else ++num, col[edge[i].to] = cot;
}
else
{
if(col[edge[i].to] == cot) continue;
else DFS(edge[i].to);
}
}
}
int main(void)
{
int a, b;
W (233)
{
in(line); ++T; if(!line) return 0;
std::memset(dfn, cnt = 0, sizeof(dfn));
std::memset(head, arr = 0, sizeof(head));
std::memset(cut, false, sizeof(cut));
std::memset(cons, false, sizeof(cons));
std::memset(col, cot = 0, sizeof(col));
for (R int i = 1; i <= line; ++i)
{
in(a), in(b);
addedge(a, b), addedge(b, a);
cons[a] = cons[b] = true;
}
for (R int i = 1; i <= 500; ++i)
if(!dfn[i] && cons[i]) t = 0, tarjan(i, i);
ans1 = 0, ans2 = 1;
for (R int i = 1; i <= 500; ++i)
{
if(!cut[i] && cons[i] && !col[i])
{
++cot; col[i] = cot;
num = siz = 0, DFS(i);
if(!num)
{
if(siz == 1) ++ans1;
else ans1 += 2, ans2 *= (siz - 1) * siz / 2;
}
if(num == 1) ++ans1, ans2 *= siz;
}
}
printf("Case %d: %lld %lld\n", T, ans1, ans2);
}
}
PS:洛谷数据太水…博主把统计方案的乘法打成加法还有80分…