题目描述
蜜蜂是最勤劳的昆虫之一。由于它们从花朵中采集花蜜和花粉,它们必须依赖森林中的树木。为简化问题,它们将森林中的 nnn 棵树编号为 000 到 n−1n-1n−1。它们不使用森林中所有的路径,而是使用特定的路径列表。一条路径连接两棵树,并且它们可以双向移动,即从一棵树直线移动到另一棵树。它们不使用不在列表中的路径。
随着科技的巨大进步,它们也改变了工作策略。它们不再在森林中所有树木上盘旋,而是针对特定的树木,主要是那些有很多花的树。因此,它们计划在一些目标树木上建造新的蜂巢。之后,它们将只从这些树上采集食物。它们还将从列表中移除一些路径,这样就不必前往没有蜂巢的树。
现在,它们希望建造蜂巢,使得即使新列表中的某条路径出现问题(例如某些鸟类或动物干扰该路径),仍然可以通过现有路径从任意一个蜂巢到达另一个蜂巢。
它们不想选择少于两棵树,并且由于建造蜂巢需要大量工作,它们需要尽可能减少蜂巢的数量。现在,给定树木和它们使用的路径,你的任务是提出一个新的蜂巢群方案。
输入格式
输入以整数 TTT(T≤50T \leq 50T≤50)开始,表示测试用例的数量。
每个测试用例以一个空行开始。下一行包含两个整数 nnn(2≤n≤5002 \leq n \leq 5002≤n≤500)和 mmm(0≤m≤200000 \leq m \leq 200000≤m≤20000),其中 nnn 表示树木的数量,mmm 表示路径的数量。接下来的 mmm 行每行包含两个整数 uuu 和 vvv(0≤u,v<n0 \leq u, v < n0≤u,v<n,u≠vu \neq vu=v),表示树 uuu 和 vvv 之间有一条路径。假设树 uuu 和 vvv 之间最多只有一条路径,并且同一条路径不会在输入中重复出现。
输出格式
对于每个测试用例,输出用例编号和提议的蜂巢群中的蜂巢数量,如果无法找到这样的蜂巢群,则输出 impossible。
注意: 数据集很大,请使用更快的 I/O\texttt{I/O}I/O 方法。
题目分析
问题本质
本题要求找到一个最小的顶点集合(蜂巢),满足以下条件:
- 顶点数量至少为 222
- 这些顶点构成的子图是边双连通的
- 顶点数量尽可能小
边双连通图是指删除任意一条边后,图仍然保持连通的图。在边双连通图中,任意两个顶点之间至少存在两条边不相交的路径。
关键观察
- 边双连通分量:一个边双连通分量本身就是一个边双连通子图
- 最小边双连通子图:我们可以在边双连通分量中选择更小的边双连通子图
- 环的性质:一个环就是一个边双连通子图,最小的环就是最小的边双连通子图
- 特殊情况:如果边双连通分量中没有环但至少有一条边,那么两个顶点就构成最小的边双连通子图
算法思路
基于以上观察,我们的解题思路如下:
-
寻找边双连通分量:
- 使用 Tarjan\texttt{Tarjan}Tarjan 算法找出所有的桥(割边)
- 通过 DFS\texttt{DFS}DFS 遍历跳过桥边,找到所有的边双连通分量
-
在每个分量中寻找最小环:
- 对于每个大小至少为 222 的边双连通分量
- 在该分量对应的子图中使用 BFS\texttt{BFS}BFS 寻找最小环
- 如果分量中没有环但大小 ≥2\geq 2≥2,说明至少有一条边,答案为 222
-
确定最终答案:
- 取所有分量中最小环长度的最小值
- 如果没有满足条件的分量,输出
impossible
算法复杂度
- 找桥:O(n+m)O(n + m)O(n+m)
- 找边双连通分量:O(n+m)O(n + m)O(n+m)
- 找最小环:最坏情况下 O(n×(n+m))O(n \times (n + m))O(n×(n+m))
- 总体复杂度:O(n×(n+m))O(n \times (n + m))O(n×(n+m)),在 n≤500n \leq 500n≤500 的范围内是可接受的
代码实现
// Beehives
// UVa ID: 12544
// Verdict: Accepted
// Submission Date: 2025-11-19
// UVa Run Time: 0.620s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 505;
const int INF = 1e9;
vector<int> graph[MAXN];
int low[MAXN], dfn[MAXN], dfsClock;
bool isBridge[MAXN][MAXN];
vector<int> comp;
int n, m;
void findBridges(int u, int p) {
low[u] = dfn[u] = ++dfsClock;
for (int v : graph[u]) {
if (v == p) continue;
if (!dfn[v]) {
findBridges(v, u);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) {
isBridge[u][v] = isBridge[v][u] = true;
}
} else {
low[u] = min(low[u], dfn[v]);
}
}
}
void dfsComp(int u) {
comp.push_back(u);
dfn[u] = 1;
for (int v : graph[u]) {
if (!dfn[v] && !isBridge[u][v]) {
dfsComp(v);
}
}
}
int findMinCycleInComponent(const vector<int>& component) {
if (component.size() < 2) return INF;
// 构建子图
unordered_map<int, int> idMap;
int compSize = component.size();
for (int i = 0; i < compSize; ++i) {
idMap[component[i]] = i;
}
vector<vector<int>> compGraph(compSize);
for (int u : component) {
for (int v : graph[u]) {
if (idMap.count(v)) {
compGraph[idMap[u]].push_back(idMap[v]);
}
}
}
// 在子图中找最小环
int minCycle = INF;
for (int start = 0; start < compSize; ++start) {
vector<int> dist(compSize, -1);
vector<int> parent(compSize, -1);
queue<int> q;
dist[start] = 0;
q.push(start);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : compGraph[u]) {
if (dist[v] == -1) {
dist[v] = dist[u] + 1;
parent[v] = u;
q.push(v);
} else if (parent[u] != v) {
minCycle = min(minCycle, dist[u] + dist[v] + 1);
}
}
}
}
return minCycle;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
for (int caseNum = 1; caseNum <= T; ++caseNum) {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
graph[i].clear();
dfn[i] = 0;
for (int j = 0; j < n; ++j) {
isBridge[i][j] = false;
}
}
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
graph[v].push_back(u);
}
// 找桥
dfsClock = 0;
for (int i = 0; i < n; ++i) {
if (!dfn[i]) {
findBridges(i, -1);
}
}
// 找边双连通分量
memset(dfn, 0, sizeof(dfn));
int answer = INF;
for (int i = 0; i < n; ++i) {
if (!dfn[i]) {
comp.clear();
dfsComp(i);
if (comp.size() >= 2) {
int minCycle = findMinCycleInComponent(comp);
if (minCycle == INF) {
// 如果没有环但是有边,答案是2
answer = min(answer, 2);
} else {
answer = min(answer, minCycle);
}
}
}
}
cout << "Case " << caseNum << ": ";
if (answer == INF) {
cout << "impossible\n";
} else {
cout << answer << "\n";
}
}
return 0;
}
总结
本题的关键在于理解边双连通图的性质和最小环的寻找方法。通过将问题分解为寻找边双连通分量和在分量中寻找最小环两个步骤,我们能够高效地解决这个问题。算法结合了 Tarjan\texttt{Tarjan}Tarjan 算法找桥和 BFS\texttt{BFS}BFS 找最小环的技巧,在合理的时间复杂度内解决了问题。
对于类似的图论问题,这种"先分解再求解"的思路往往能够简化问题,提高解题效率。
1312

被折叠的 条评论
为什么被折叠?



