题解:洛谷 P1330 封锁阳光大学
【题目来源】
【题目描述】
曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。
阳光大学的校园是一张由 \(n\) 个点构成的无向图,\(n\) 个点之间由 \(m\) 条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。
询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。
【输入】
第一行两个正整数 \(n,m\),表示节点数和边数。
接下来 \(m\) 行,每行两个整数 \(u,v\),表示点 \(u\) 到点 \(v\) 之间有道路相连。
【输出】
仅一行,如果河蟹无法封锁所有道路,则输出 Impossible,否则输出一个整数,表示最少需要多少只河蟹。
【输入样例】
3 3
1 2
1 3
2 3
【输出样例】
Impossible
【核心思想】
-
问题分析:给定无向图,需要选择最少数量的节点进行"封锁",使得所有边都被覆盖(每条边至少有一个端点被选中),且相邻节点不能同时被选中。这是一个二分图最小点覆盖问题的变形。若图不是二分图(存在奇环),则无法满足条件。
-
算法选择:
- 二分图判定(染色法):使用 DFS/BFS 对图进行黑白染色,判断是否为二分图
- 贪心策略:对于每个连通块,选择两种颜色中数量较少的那一种放置河蟹
-
关键步骤:
- 读入 \(n\) 个节点、\(m\) 条边,构建无向图邻接表
- 对每个未染色的连通块进行 DFS 染色:
- 从节点 \(u\) 开始染颜色 \(c\)(\(1\) 或 \(2\))
- 递归地将邻居染成相反颜色 \(3-c\)
- 若发现邻居已染相同颜色,则图不是二分图,输出
Impossible
- 统计每个连通块中两种颜色的数量 \(cnt[1]\) 和 \(cnt[2]\)
- 累加答案 \(ans += \min(cnt[1], cnt[2])\)(选择较少的颜色放置河蟹)
- 孤立点(度数为 \(0\))无需河蟹,直接跳过
-
时间/空间复杂度:
- 时间复杂度:\(O(n + m)\),每个节点和边只被访问一次
- 空间复杂度:\(O(n + m)\),存储邻接表、颜色数组和度数数组
-
二分图染色与最小点覆盖的核心思想:
- 二分图性质:图中不存在奇数长度的环,可以将节点分成两个集合,集合内部无边
- 染色法判定:通过两种颜色染色,相邻节点颜色不同,若冲突则不是二分图
- 最小点覆盖:在二分图中,选择某一颜色集合的所有节点即可覆盖所有边
- 最优策略:对每个连通块选择颜色数量较少的集合,保证全局最优
- 适用于资源分配、冲突避免等场景
【算法标签】
普及 #二分图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 100005 * 2;
int n, m, ans; // n: 点数,m: 边数,ans: 最少需要放置的河蟹数量
int h[N], e[M], ne[M], idx; // 邻接表
int color[N]; // 染色数组,0表示未染色,1表示第一种颜色,2表示第二种颜色
int cnt[3]; // 记录每个连通块中两种颜色的数量
int d[N]; // 记录每个点的度数
void add(int a, int b) // 添加无向边
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
d[a]++; // 增加点的度数
}
bool dfs(int u, int c) // 深度优先搜索,判断是否为二分图
{
color[u] = c; // 染色
cnt[c]++; // 对应颜色的计数加1
for (int i = h[u]; i != -1; i = ne[i]) // 遍历邻居
{
int v = e[i];
if (color[v] == 0) // 如果邻居未染色
{
if (dfs(v, 3 - c) == false) // 递归染色
return false;
}
else if (color[v] == c) // 如果邻居颜色相同
{
return false; // 不是二分图
}
}
return true; // 是二分图
}
int main()
{
cin >> n >> m; // 输入点数和边数
memset(h, -1, sizeof(h)); // 初始化邻接表
while (m--) // 输入每条边
{
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
bool flag = true; // 标记是否为二分图
for (int i = 1; i <= n; i++) // 遍历每个点
{
if (color[i] == 0) // 如果未染色
{
// 孤立点不需要河蟹
if (d[i] == 0) // 如果是孤立点
continue; // 跳过
cnt[1] = cnt[2] = 0; // 重置计数器
if (dfs(i, 1) == false) // 如果不是二分图
{
flag = false; // 标记为不是二分图
break; // 退出循环
}
ans += min(cnt[1], cnt[2]); // 累加最少需要的河蟹数量
}
}
if (!flag) // 如果不是二分图
cout << "Impossible" << endl; // 输出不可能
else
cout << ans << endl; // 输出最少需要的河蟹数量
return 0;
}
【运行结果】
3 3
1 2
1 3
2 3
Impossible
浙公网安备 33010602011771号