题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 个顶点、\(M\) 条边的无向简单图,可以删除任意边,求使图变为二分图的最小删除边数。二分图的判定条件是图中不存在奇环,等价于可对顶点进行黑白染色使得每条边两端颜色不同。这是一个二分图判定 + DFS染色问题,关键在于通过染色发现冲突边(同色边),并以最小删除代价消除所有冲突。
-
算法选择:
- DFS二分图染色:从任意起点开始,尝试用两种颜色对连通块进行染色,若发现邻接点已染同色则产生冲突
- 枚举起点策略:由于图可能不连通,且每个连通块的染色方案有翻转两种选择,通过枚举每个节点作为DFS起点,统计所有可能的染色方案中的最小冲突数
- 冲突边计数:每条冲突边在DFS过程中会被两个端点各统计一次,最终答案需除以 \(2\)
-
关键步骤:
- 建图:读入 \(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\)(每条冲突边被统计两次)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot (N + M))\),枚举 \(N\) 个起点,每次DFS遍历全图 \(O(N + M)\)
- 空间复杂度:\(O(N + M)\),邻接表 + 访问标记 + 颜色数组
-
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
浙公网安备 33010602011771号