题解:洛谷 P1330 封锁阳光大学

【题目来源】

洛谷:P1330 封锁阳光大学 - 洛谷

【题目描述】

曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。

阳光大学的校园是一张由 \(n\) 个点构成的无向图,\(n\) 个点之间由 \(m\) 条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。

询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。

【输入】

第一行两个正整数 \(n,m\),表示节点数和边数。
接下来 \(m\) 行,每行两个整数 \(u,v\),表示点 \(u\) 到点 \(v\) 之间有道路相连。

【输出】

仅一行,如果河蟹无法封锁所有道路,则输出 Impossible,否则输出一个整数,表示最少需要多少只河蟹。

【输入样例】

3 3
1 2
1 3
2 3

【输出样例】

Impossible

【核心思想】

  1. 问题分析:给定无向图,需要选择最少数量的节点进行"封锁",使得所有边都被覆盖(每条边至少有一个端点被选中),且相邻节点不能同时被选中。这是一个二分图最小点覆盖问题的变形。若图不是二分图(存在奇环),则无法满足条件。

  2. 算法选择

    • 二分图判定(染色法):使用 DFS/BFS 对图进行黑白染色,判断是否为二分图
    • 贪心策略:对于每个连通块,选择两种颜色中数量较少的那一种放置河蟹
  3. 关键步骤

    • 读入 \(n\) 个节点、\(m\) 条边,构建无向图邻接表
    • 对每个未染色的连通块进行 DFS 染色:
      • 从节点 \(u\) 开始染颜色 \(c\)\(1\)\(2\)
      • 递归地将邻居染成相反颜色 \(3-c\)
      • 若发现邻居已染相同颜色,则图不是二分图,输出 Impossible
    • 统计每个连通块中两种颜色的数量 \(cnt[1]\)\(cnt[2]\)
    • 累加答案 \(ans += \min(cnt[1], cnt[2])\)(选择较少的颜色放置河蟹)
    • 孤立点(度数为 \(0\))无需河蟹,直接跳过
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + m)\),每个节点和边只被访问一次
    • 空间复杂度:\(O(n + m)\),存储邻接表、颜色数组和度数数组
  5. 二分图染色与最小点覆盖的核心思想

    • 二分图性质:图中不存在奇数长度的环,可以将节点分成两个集合,集合内部无边
    • 染色法判定:通过两种颜色染色,相邻节点颜色不同,若冲突则不是二分图
    • 最小点覆盖:在二分图中,选择某一颜色集合的所有节点即可覆盖所有边
    • 最优策略:对每个连通块选择颜色数量较少的集合,保证全局最优
    • 适用于资源分配、冲突避免等场景

【算法标签】

普及 #二分图

【代码详解】

#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
posted @ 2026-06-14 10:48  团爸讲算法  阅读(16)  评论(0)    收藏  举报