题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 支球队的比赛记录(每支球队进行两场比赛),需要选出 \(K\) 支球队,使得这 \(K\) 支球队中任意两支都没有比赛过。将比赛关系建模为图:球队为节点,比赛为边。问题转化为在图中选出 \(K\) 个互不相邻的节点(独立集)。由于每支球队恰好参加两场比赛,图由若干个环组成,是二分图。

  2. 算法选择

    • 二分图染色:使用 DFS 对图进行黑白染色,将节点分为两个集合
    • 贪心选择:选择较大的独立集(颜色数量较多的集合),从中取 \(K\) 个节点
  3. 关键步骤

    • 读入 \(N\) 支球队,构建无向图邻接表 \(G\)
    • 读入目标数量 \(K\)
    • 对每个未染色的连通块进行 DFS 染色:
      • 将起始节点染成红色(\(color = 1\)
      • 递归地将邻居染成相反颜色(黑色,\(color = -1\)
      • 若发现邻居已染相同颜色,则不是二分图(本题保证是二分图)
    • 统计所有红色节点存入 \(ans\) 数组
    • \(|ans| \geq K\),输出前 \(K\) 个红色节点;否则输出 \(0\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每个节点和边只被访问一次(总边数为 \(N\),因为每支球队两场比赛)
    • 空间复杂度:\(O(N)\),存储邻接表和颜色数组
  5. 二分图独立集的核心思想

    • 二分图性质:节点可分为两个集合,集合内部无边,所有边都连接两个集合
    • 独立集:同一颜色集合内的节点互不相邻,构成独立集
    • 最大独立集:选择两个颜色集合中较大的一个,即为最大独立集
    • 本题变体:只需找到大小至少为 \(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
posted @ 2026-06-27 14:19  团爸讲算法  阅读(9)  评论(0)    收藏  举报