洛谷P3225 [HNOI2012] 矿场搭建 题解
题意:
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入格式:
输入文件有若干组数据。
每组数据的第一行是一个正整数 $N\ (N \le 500)$,表示工地的隧道数。
接下来的 $N$ 行每行是用空格隔开的两个整数 $S$ 和 $T$,表示挖煤点 $S$ 与挖煤点 $T$ 由隧道直接连接。
输入数据以 $0$ 结尾。
输出格式:
对于每组数据,输出一行。
第 $i$ 行组数据以 $\verb!Case i: !$ 开始(注意大小写,$\verb!Case!$ 与 $\verb!i!$ 之间有空格,$\verb!i!$ 与 $\verb!:!$ 之间无空格,$\verb!:!$ 之后有空格)。
其后是用空格隔开的两个正整数
- 第一个正整数表示对于第 $i$ 组输入数据至少需要设置几个救援出口。
- 第二个正整数表示对于第 $i$ 组输入数据不同最少救援出口的设置方案总数。
输入数据保证答案小于 $2^{64}$。输出格式参照以下输入输出样例。
数据范围:
对于每组数据,设 $m$ 为各组 $S, T$ 中最大值,$1 \le m \le 10^3$ 。
保证各组 $S, T$ 构成的集合 $V = [1, m] \cap \mathbb Z$ 。$V$ 中任意两点连通。
这个输出一股 UVA 的味道,结果还真是 UVA 的题(甚至是弱化版),链接放题解最后面。
首先一个矿洞坍塌就相当于删掉了这个点,要求此时其他任何的点都能到达出口。
我们考察图中的点双连通分量 (2–vertex–connected component) ,以下简称 VCC 吧。
如果 VCC 含有大于 $1$ 个割点 (cut vertex) ,那么无论哪个割点挂了,都可以跑到另一个 VCC 的出口
这相当于合并了两个 VCC,因此这部分没有任何贡献。
如果 VCC 含有 $1$ 个割点,那么当这个割点挂了,就得在 VCC 里其他地方放一个出口
设这个 VCC 的大小为 $a$ ,则对第一问的贡献为 $1$ ,第二问的贡献为 $a - 1$ 。
如果 VCC 没有割点,那么就需要放两个出口,则对第一问的贡献为 $2$ ,第二问的贡献为 $\frac{a(a - 1)}{2}$ 。
我看题解区有人跑了个 DFS ,有点迷惑,只需要像点双缩点一样不就好了么。
时间复杂度 $\mathcal{O}(n + m)$ ,过于轻松通过本题。
代码:
#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
typedef long long ll;
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define N ((int)(555))
int n, m, vcnt, cut[N], low[N], dfn[N];
stack<int> stk; ll res1, res2;
vector<int> vec[N], vcc[N];
void Tarjan(int u, int rt)
{
int c = 0; stk.push(u);
static int tim = 0; low[u] = dfn[u] = ++tim;
for(int v : vec[u])
{
if(!dfn[v])
{
Tarjan(v, rt); down(low[u], low[v]);
if(low[v] >= dfn[u])
{
if(u != rt || ++c > 1) cut[u] = true;
vcc[++vcnt].push_back(u);
for(int p = 0; p != v; ) {
p = stk.top(); stk.pop(); vcc[vcnt].push_back(p);
}
}
}else down(low[u], dfn[v]);
}
}
void work()
{
stk = stack<int>(); static int Case = 0; ++Case;
for(int i = 1; i <= n; i++)
{
vec[i].clear(), vcc[i].clear();
cut[i] = low[i] = dfn[i] = 0;
}
n = vcnt = res1 = 0; res2 = 1;
for(int i = 1, x, y; i <= m; i++)
{
cin >> x >> y; up(n, max(x, y));
vec[x].push_back(y); vec[y].push_back(x);
}
Tarjan(1, 1);
for(int i = 1; i <= vcnt; i++)
{
int a = vcc[i].size(), b = 0;
for(int v : vcc[i]) b += cut[v];
if(b == 1) { res1 += 1, res2 *= a - 1; }
if(b == 0) { res1 += 2, res2 *= (ll)a * (a - 1) / 2; }
}
cout << "Case " << Case << ": " << res1 << ' ' << res2 << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
while(cin >> m && m) work();
return 0;
}
本题是三倍经验:
参考文献:
[1] https://www.luogu.com.cn/article/qcoxqeis
题外话:
好困。点双好久不写都快忘记怎么写了。
另外今天试图拉 cxy 入坑计算机(大雾)
甚至写了一个极具争议性且极为片面甚至极具煽动性的表格
本图纯属搞笑,切勿当真。
另外强烈建议各位选自己喜欢的专业,不要理会什么大势所趋。
不过会看我博客的多半都是选计算机科学与技术的吧(