题解:洛谷 P14552 乌拉尔冰球赛
【题目来源】
洛谷:P14552 [ROI 2013 Day1] 乌拉尔冰球赛 - 洛谷
【题目描述】
为了普及冰球运动并提高乌拉尔地区冰球队的技术水平,组织了一场全乌拉尔锦标赛。锦标赛邀请了来自乌拉尔各城市的 \(N\) 支冰球队参加。
在前两轮比赛后,每支球队各进行了一场比赛,结果发现参赛队伍过多。锦标赛组织者决定只允许 \(K\) 支球队继续参赛,且这些球队中任意两支在前两轮比赛中均未相遇。
需要编写一个程序,找出满足条件的 \(K\) 支球队集合,或者输出无法实现的消息。如果存在多个符合条件的集合,只需找出其中任意一个。
【输入】
输入文件的第一行包含一个数字 \(N\)(\(2 \leqslant N \leqslant 100,000\),\(N\) 为偶数)。
随后的 \(N\) 行包含所有已进行比赛的描述。每场比赛的描述由两个不超过 \(N\) 的自然数组成——表示参加比赛的球队编号。
前 \(N/2\) 行对应第一轮的比赛,其余行对应第二轮的比赛。
输入文件的最后一行包含一个数字 \(K\)(\(2 \leqslant K \leqslant N\))。
保证每支球队恰好进行了两场比赛:一场在第一轮,一场在第二轮。
【输出】
输出文件应包含:如果无解,则仅输出一个数字 \(0\);否则输出 \(K\) 个不同的数字——所选球队的编号。
【输入样例】
6
1 2
3 5
4 6
2 3
4 5
1 6
3
【输出样例】
1 4 3
【核心思想】
-
问题分析:给定 \(N\) 支球队的比赛记录(每支球队进行两场比赛),需要选出 \(K\) 支球队,使得这 \(K\) 支球队中任意两支都没有比赛过。将比赛关系建模为图:球队为节点,比赛为边。问题转化为在图中选出 \(K\) 个互不相邻的节点(独立集)。由于每支球队恰好参加两场比赛,图由若干个环组成,是二分图。
-
算法选择:
- 二分图染色:使用 DFS 对图进行黑白染色,将节点分为两个集合
- 贪心选择:选择较大的独立集(颜色数量较多的集合),从中取 \(K\) 个节点
-
关键步骤:
- 读入 \(N\) 支球队,构建无向图邻接表 \(G\)
- 读入目标数量 \(K\)
- 对每个未染色的连通块进行 DFS 染色:
- 将起始节点染成红色(\(color = 1\))
- 递归地将邻居染成相反颜色(黑色,\(color = -1\))
- 若发现邻居已染相同颜色,则不是二分图(本题保证是二分图)
- 统计所有红色节点存入 \(ans\) 数组
- 若 \(|ans| \geq K\),输出前 \(K\) 个红色节点;否则输出 \(0\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),每个节点和边只被访问一次(总边数为 \(N\),因为每支球队两场比赛)
- 空间复杂度:\(O(N)\),存储邻接表和颜色数组
-
二分图独立集的核心思想:
- 二分图性质:节点可分为两个集合,集合内部无边,所有边都连接两个集合
- 独立集:同一颜色集合内的节点互不相邻,构成独立集
- 最大独立集:选择两个颜色集合中较大的一个,即为最大独立集
- 本题变体:只需找到大小至少为 \(K\) 的独立集,选择红色集合即可
- 适用于匹配问题、资源分配、冲突避免等场景
【算法标签】
普及 #二分图
【代码详解】
#include <bits/stdc++.h> // 包含所有标准库头文件
using namespace std; // 使用标准命名空间
const int N = 100010; // 定义最大节点数
int n, k; // 节点数n,所需红色节点数k
vector<int> G[N], ans; // 邻接表G存储图,ans存储所有红色节点
int color[N]; // 颜色数组,0表示未染色,1表示红色,-1表示黑色
bool flag; // 是否为二分图的标志
void dfs(int x) // 深度优先搜索函数
{
if (color[x]==1) // 如果当前节点是红色
ans.push_back(x); // 将红色节点加入答案数组
for (int i = 0; i < G[x].size(); i++) // 遍历当前节点的所有邻居
{
int y = G[x][i]; // 获取邻居节点
if (color[x] == color[y]) // 如果当前节点和邻居颜色相同
{
flag = false; // 不是二分图
return; // 返回
}
if (color[y] == 0) // 如果邻居节点未染色
{
color[y] = -color[x]; // 将邻居染成相反颜色
dfs(y); // 递归处理邻居节点
}
}
}
int main() // 主函数
{
cin >> n; // 输入节点数
for (int i = 1; i <= n; i++) // 构建图的邻接表
{
int u, v; // 边的两个端点
cin >> u >> v; // 输入边的信息
G[v].push_back(u); // 添加无向边
G[u].push_back(v);
}
cin >> k; // 输入需要输出的红色节点数量
for (int i = 1; i <= n; i++) // 遍历所有节点
{
if (color[i] == 0) // 如果节点未染色
{
color[i] = 1; // 染成红色
dfs(i); // 从该节点开始深度优先搜索
}
}
if (ans.size()>=k) // 如果红色节点数量满足要求
{
for (int i=0; i<k; i++) // 输出前k个红色节点
cout << ans[i] << " "; // 输出红色节点
cout << endl; // 换行
}
else // 如果红色节点数量不足
{
cout << 0 << endl; // 输出0
}
return 0; // 程序正常结束
}
【运行结果】
6
1 2
3 5
4 6
2 3
4 5
1 6
3
1 3 4
浙公网安备 33010602011771号