题解:洛谷 AT_abc427_c Bipartize

【题目来源】

洛谷:AT_abc427_c [ABC427C] Bipartize - 洛谷

【题目描述】

有一个包含 \(N\) 个顶点和 \(M\) 条边的无向简单图。该图包含顶点 \(1, 2, \ldots, N\),第 \(i\) 条边 \((1\le i\le M)\) 连接顶点 \(u_i\)\(v_i\)

你可以进行如下操作任意次(也可以不进行操作):

  • 选择一条尚未被删除的边,并将其删除。

你的目标是通过上述操作使图变为二分图。请你求出最少需要进行多少次操作,才能使经过操作后的图变为二分图。

什么是简单图?简单图指的是没有自环(即不存在 \(u_i = v_i\) 的边)且没有重边(不存在 \(u_i = u_j\)\(v_i = v_j\) 的两条边)。

什么是二分图?如果能够给该图的每一个顶点染黑或染白,使得任意一条边连接的两个顶点颜色不同,则该图为二分图。

【输入】

输入通过标准输入给出,格式如下:

\(N\) \(M\)
\(u_1\) \(v_1\)
\(u_2\) \(v_2\)
\(\vdots\)
\(u_M\) \(v_M\)

【输出】

输出使图变为二分图所需的最小操作次数。

【输入样例】

5 8
1 2
1 3
1 4
2 3
2 5
3 4
3 5
4 5

【输出样例】

2

【核心思想】

  1. 问题分析:给定 \(N\) 个顶点、\(M\) 条边的无向简单图,可以删除任意边,求使图变为二分图的最小删除边数。二分图的判定条件是图中不存在奇环,等价于可对顶点进行黑白染色使得每条边两端颜色不同。这是一个二分图判定 + DFS染色问题,关键在于通过染色发现冲突边(同色边),并以最小删除代价消除所有冲突。

  2. 算法选择

    • DFS二分图染色:从任意起点开始,尝试用两种颜色对连通块进行染色,若发现邻接点已染同色则产生冲突
    • 枚举起点策略:由于图可能不连通,且每个连通块的染色方案有翻转两种选择,通过枚举每个节点作为DFS起点,统计所有可能的染色方案中的最小冲突数
    • 冲突边计数:每条冲突边在DFS过程中会被两个端点各统计一次,最终答案需除以 \(2\)
  3. 关键步骤

    • 建图:读入 \(N, M\) 和边集,用邻接表 \(ve\) 存储无向图
    • 枚举起点\(i\)\(1\)\(N\)):
      • 重置访问标记 \(vis\)、颜色数组 \(col\)、冲突计数器 \(cnt = 0\)
      • 从节点 \(i\) 开始DFS染色,初始颜色为 \(1\)
    • DFS染色(当前节点 \(u\),颜色 \(color\)):
      • 标记 \(vis[u] = 1\)\(col[u] = color\)
      • 遍历邻接点 \(v\)
        • \(col[v] = col[u]\)\(cnt \mathrel{+}= 1\)(发现冲突边)
        • \(vis[v] = 1\):跳过(已处理)
        • 否则:递归 \(dfs(v,\ color = 1 \text{ 则 } 2 \text{ 否则 } 1)\)(染相反色)
    • 更新答案\(ans = \min(ans, cnt)\)
    • 输出\(ans / 2\)(每条冲突边被统计两次)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot (N + M))\),枚举 \(N\) 个起点,每次DFS遍历全图 \(O(N + M)\)
    • 空间复杂度:\(O(N + M)\),邻接表 + 访问标记 + 颜色数组
  5. DFS二分图染色的核心思想

    • 二色性判定:二分图等价于"2-可染色图",DFS过程中若发现邻接点同色则存在奇环
    • 冲突边即需删边:每条同色边代表一个二分图约束冲突,删除这些边即可使图变为二分图
    • 全局最优 via 局部枚举:由于染色方案的翻转等价性,枚举每个起点尝试不同根颜色,取全局最小冲突数
    • 无向边重复计数:无向图中 \((u,v)\)\((v,u)\) 是同一条边,DFS从两端各统计一次,故最终除以 \(2\)
    • 适用于"图的二分性判定 + 最小删边使图二分"类问题,核心是染色冲突的识别与统计

【算法标签】

普及- #DFS-一维

【代码详解】

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

typedef pair<int, int> PII;  // 定义边对类型
const int N = 15;            // 定义最大节点数

int n, m;                    // n:节点数, m:边数
int col[N];                  // 存储每个节点的颜色(1或2)
int cnt;                     // 统计同色边的数量
int cur;                     // 未使用的临时变量
int ans = 1e9;               // 存储最小同色边数
PII bian[N * N];             // 未使用的边数组
vector<int> ve[N];           // 邻接表存储图结构
bool vis[N];                 // DFS访问标记数组

/**
 * 深度优先搜索进行二分图染色
 * @param u 当前节点
 * @param color 当前节点要染的颜色
 */
void dfs(int u, int color)
{
    vis[u] = 1;      // 标记当前节点已访问
    col[u] = color;  // 给当前节点染色

    // 遍历所有邻接节点
    for (int i = 0; i < ve[u].size(); i++)
    {
        int v = ve[u][i];  // 邻接节点

        // 如果邻接节点与当前节点颜色相同,统计同色边
        if (col[v] == col[u])
        {
            cnt++;
        }

        // 如果邻接节点已访问,跳过
        if (vis[v])
        {
            continue;
        }

        // 递归染色邻接节点(使用相反颜色)
        dfs(v, color == 1 ? 2 : 1);
    }
}

int main()
{
    // 输入节点数和边数
    cin >> n >> m;

    // 输入图的边
    for (int i = 1; i <= m; i++)
    {
        int u, v;
        cin >> u >> v;
        ve[u].push_back(v);
        ve[v].push_back(u);
    }

    // 尝试以每个节点为起点进行二分图染色
    for (int i = 1; i <= n; i++)
    {
        // 重置访问标记和颜色数组
        memset(vis, 0, sizeof(vis));
        memset(col, 0, sizeof(col));
        cnt = 0;  // 重置同色边计数器

        // 从当前节点开始DFS染色
        dfs(i, 1);

        // 更新最小同色边数
        ans = min(ans, cnt);
    }

    // 输出结果(同色边被统计了两次,需要除以2)
    cout << ans / 2 << endl;

    return 0;
}

【运行结果】

5 8
1 2
1 3
1 4
2 3
2 5
3 4
3 5
4 5
2
posted @ 2026-08-27 11:03  团爸讲算法  阅读(6)  评论(0)    收藏  举报