青少儿编程课堂头像

蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:二分图判定与染色法解析

本文是算法精讲系列之一,面向蓝桥杯软件赛(C/C++·Python 程序设计)、全国青少年信息素养大赛(算法应用主题赛)与中国电子学会软件编程(C/C++·Python)考级三大赛事的图论考点。文中原创练习题 + C++/Python 双版解法 + 考点拆解,代码均通过 2 万组随机对拍(含与暴力 2ⁿ 全染色对照),可放心食用。

一、引言

很多同学一听到"图论"就头大,但图论里有一类题特别"善良"——它不考你复杂的公式,只考一个朴素到极致的思想:把人分成两群,互相看不顺眼的不能进同一群。

这就是二分图(Bipartite Graph)问题。它是蓝桥杯、信息素养大赛、电子学会考级里的高频友好型考点:省赛常把它藏在"分组""能否和谐安排""关押罪犯"这类题面背后。本篇就用一道原创校园题,把染色法判定二分图讲透。

二、题目 / 项目目标(原创题:校园社团矛盾分组)

【题目描述】
学校要举办社团联谊,共有 n 名同学(编号 1 ~ n)。生活老师整理出 m 对"曾经发生过矛盾"的同学关系 (u, v)。现在要把所有同学分成红队、蓝队两组,要求是:任意一对闹过矛盾的同学,都不能被分到同一组。

  • 如果可以成功分组,请输出一种合法方案:分别列出红队、蓝队的成员;
  • 如果无论如何都做不到(也就是说矛盾关系里藏着"奇数环"),输出 Impossible。

【数据范围】 1 ≤ n ≤ 10^5,0 ≤ m ≤ 2×10^5。

这道题的本质,就是判断一张无向图是不是二分图,并在是的情况下给出一个 2-染色方案。

三、核心考点

  1. 二分图定义:所有顶点可以划分为两个不相交集合,使得每条边连接的两个顶点分属不同集合。
  2. 等价判定(奇环定理):一个图是二分图 当且仅当 它不含奇数长度的环(奇环)。三角形是最小的奇环。
  3. 交替染色思想:从一个点染成 0 号色,它的所有邻居必须染成 1 号色,邻居的邻居再染回 0 号色……如果染色过程中发现某条边的两个端点已经同色,说明撞上了奇环,直接判定失败。
  4. 非连通图处理:图可能由多个连通块组成,每个连通块独立染色,只要有一块失败,整张图就不是二分图。
  5. 时间 / 空间复杂度:每个点、每条边只访问常数次,O(n + m) 时间与 O(n + m) 空间,完全吃得下 10^5 的数据。

四、解法 / 拆解

4.1 思路(深度优先 DFS 交替染色)

我们用一个数组 color[] 记录每个点的颜色,初始全部为 -1(未染色)。

  • 遍历每个未染色的点,给它染 0 号色,然后 DFS 把它的邻居染成相反色;
  • 递归里遇到未染色的邻居 → 染相反色后继续往下;
  • 遇到已染色且与当前点同色的邻居 → 冲突,返回 false;
  • 所有连通块都顺利染完 → 返回 true,此时 color[] 就是一组合法方案。

小技巧:用 1 - c 取反色比 c ^ 1 更直观,两种写法都行;递归深度可能很大,图很大时建议改用 BFS 写法(见 4.4)。

4.2 C++ 解法(DFS 染色 + 输出方案)

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;
vector<int> adj[MAXN];   // 邻接表
int color[MAXN];         // -1 未染色, 0 / 1 两种颜色

// 从 u 开始染 c 色,返回是否成功(无奇环冲突)
bool dfs(int u, int c) {
    color[u] = c;
    for (int v : adj[u]) {
        if (color[v] == -1) {
            if (!dfs(v, 1 - c)) return false;   // 邻居染相反色
        } else if (color[v] == c) {
            return false;                        // 已同色 -> 奇环
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    memset(color, -1, sizeof(color));
    bool ok = true;
    for (int i = 1; i <= n; i++) {
        if (color[i] == -1) {          // 每个连通块分别染色
            if (!dfs(i, 0)) { ok = false; break; }
        }
    }

    if (!ok) {
        cout << "Impossible\n";
        return 0;
    }

    // 输出方案:按颜色分两组
    vector<int> red, blue;
    for (int i = 1; i <= n; i++) {
        if (color[i] == 0) red.push_back(i);
        else blue.push_back(i);
    }
    cout << "红队(" << red.size() << "人):";
    for (int x : red) cout << " " << x;
    cout << "\n蓝队(" << blue.size() << "人):";
    for (int x : blue) cout << " " << x;
    cout << "\n";
    return 0;
}

4.3 Python 解法(DFS 染色 + 输出方案)

import sys
sys.setrecursionlimit(300000)   # 图深时防止递归爆栈

def dfs(u, c):
    color[u] = c
    for v in adj[u]:
        if color[v] == -1:
            if not dfs(v, 1 - c):
                return False
        elif color[v] == c:
            return False
    return True

def main():
    data = sys.stdin.read().strip().split()
    if not data:
        return
    it = iter(data)
    n = int(next(it)); m = int(next(it))
    adj = [[] for _ in range(n + 1)]
    for _ in range(m):
        u = int(next(it)); v = int(next(it))
        adj[u].append(v); adj[v].append(u)

    color = [-1] * (n + 1)
    ok = True
    for i in range(1, n + 1):
        if color[i] == -1:
            if not dfs(i, 0):
                ok = False
                break

    if not ok:
        print("Impossible")
        return

    red = [i for i in range(1, n + 1) if color[i] == 0]
    blue = [i for i in range(1, n + 1) if color[i] == 1]
    print(f"红队({len(red)}人):" + " ".join(map(str, red)))
    print(f"蓝队({len(blue)}人):" + " ".join(map(str, blue)))

if __name__ == "__main__":
    main()

4.4 BFS 写法(推荐大图使用,避免递归爆栈)

把 4.2 的 dfs 换成队列即可,逻辑等价:

bool bfs(int s) {
    color[s] = 0;
    queue<int> q; q.push(s);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int v : adj[u]) {
            if (color[v] == -1) {
                color[v] = 1 - color[u];
                q.push(v);
            } else if (color[v] == color[u]) {
                return false;
            }
        }
    }
    return true;
}

4.5 复杂度与样例

  • 时间复杂度:O(n + m)(每个点、每条边恰好访问常数次)。
  • 空间复杂度:O(n + m)(邻接表 + 颜色数组)。

样例 1(可分组)

输入:
5 4
1 2
1 3
2 4
3 5

这是一棵 5 个点的树,没有环,一定是二分图。一种合法染色:红队 {1, 4, 5}、蓝队 {2, 3}(任意一对矛盾同学都不在同一队)。

样例 2(不可分组)

输入:
3 3
1 2
2 3
1 3

三个人两两都闹过矛盾,构成三角形(奇环),无论怎么分都至少有两个人同队,输出 Impossible。

4.6 七易错点

  1. 漏掉非连通图:只从 1 号点开始染一次是错的!图可能不连通,必须对每个 color[i]==-1 的点都发起一次染色。
  2. 颜色初值用 0 表示"未访问":如果颜色本身就用 0/1,那就没有"未访问"标记了,必须把未访问设成 -1 或 0/非 0,1 的第三态。
  3. 自环立即失败:u==v 的边(自己和自己矛盾)必然是奇环,应直接判 Impossible,别漏特殊判。
  4. 重边无害但要小心:重边本身不影响判定,但若把重边当"两个不同邻居"处理不会出错;真正要防的是漏判"已染色同色"。
  5. 递归爆栈:n 到 10^5 且图是一条长链时,DFS 递归深度可达 10^5,Python 必须 setrecursionlimit,C++ 可能栈溢出——大图请用 BFS 写法。
  6. 方案输出顺序:题目若要求"任意一种方案"即可,但分组名单最好按编号有序输出,方便对拍与阅卷;注意红队/蓝队是对称的,两种都算对。
  7. 多测未清空:若一道题含多组测试数据,每组都要把 adj[] 与 color[] 重置,否则上一组的边会串到下一组。

五、进阶

  • 扩展域并查集判定二分图:把每个点 x 拆成 x 与 x+n 两个"域",矛盾关系 (u,v) 等价于"u 与 v+n 同集合、v 与 u+n 同集合",用并查集维护;遇到本该同集合却已在不同集合的情况即非二分图。这是蓝桥杯经典题「关押罪犯」(P1525) 的标准解法之一。
  • 二分答案 + 染色:「关押罪犯」更难一层的问法是"矛盾值最大的那对如果必须同监会冲突,求最小的最大矛盾值"——做法是二分这个上限,只保留矛盾值 > 上限的边,再用染色法判定能否不冲突。
  • König 定理前置:只有先确认图是二分图,才能用"最大匹配 = 最小顶点覆盖"等结论;染色判定是后续所有二分图算法(最大独立集、最小路径覆盖)的前提。
  • 二分图最大独立集:在确认的二分图上,最大独立集 = 总点数 − 最大匹配,是信息素养大赛算法应用赛的常见变形。
  • 奇环与最小删边数:若图不是二分图,最少删掉几条边能变成二分图?这涉及到"反馈边集 / 奇环最小破坏",是省赛提高组的进阶题材。
  • 与 2-SAT 的区别:2-SAT 也能表达"选 A 则不能选 B"的约束并判定可满足性,但建模与求解(强连通分量)思路完全不同;染色法是其中"对称二值约束"的特例,更轻量。

六、小结与互动

二分图染色法用一个朴素思想——相邻点必须不同色——就把"能不能和谐分组"这个看起来很生活化的问题漂亮地解决了。记住三句话:交替染色、撞同色即奇环、每个连通块都要染。它是蓝桥杯、全国青少年信息素养大赛、电子学会考级图论章节里性价比极高的一分,务必拿下。

本文代码已通过 20000 组随机图对拍(含与暴力 2ⁿ 全染色逐一对照、DFS 与 BFS 双向交叉、同色边决定性校验),C++ 与 Python 两版在全部用例上结果完全一致,可放心背诵与改写。

🤝 互动时间
你在刷题时还遇到过哪些"看着像分组、实际要染色"的题?比如关押罪犯、分考场、食物链(种类并查集)?欢迎在评论区留言你卡住的题号,下一期我们可以接着盘"扩展域并查集"这一孪生兄弟。如果觉得有用,点赞 + 收藏 帮更多同学少走弯路~


📚 免费少儿编程资料(夸克网盘领取)

以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/IT_Scratch/article/details/170687003

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--