染色法
食用指南:
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目侧重题目分析,代码实现,以及必要的代码理解误区
题目描述:
-
给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。输入格式
第一行包含三个整数 n1、 n2 和 m。
接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。输出格式
输出一个整数,表示二分图的最大匹配数。数据范围
1≤n1,n2≤500,
1≤u≤n1,
1≤v≤n2,
1≤m≤105
输入样例:
2 2 4
1 1
1 2
2 1
2 2
输出样例:
2 -
题目来源:https://www.acwing.com/problem/content/863/
题目分析:
- 染色法经典题目:针对每点,若将其染为x色,则将其邻接点染为另一色。
最终图中每一点都成功着色,则该图为二分图。
反之若某点出现染色矛盾,既应该染x色,又应该染另一色,则该图不为二分图。 - 染色法分为两种:dfs实现 & bfs实现
下面来讲同为O(n)的两种染色法判定二分图
算法原理:
模板算法:
染色法:
1. 二分图:
- 定义:
对于每一条边,将其两个端点放置在不同的两个集合中。
若最终图中所有点完美被划分到两个不同集合中,则该图为二分图。 - 性质:
可以用两种颜色无冲突的着色图中所有点。 - 举例:
二分图:
非二分图:
2. 两大步骤:
-
对于每一个点,不论dfs还是bfs,任务有二:
为当前未着色点着色 & 查看邻接点和当前点是否存在冲突。
-
为当前未着色的x点染色y:
-
遍历当前x点的邻接点,逐个判断是否与当前点冲突:
若邻接点已经着色,且为y,则出现着色冲突,直接判定非二分图。
若邻接点已经着色,且为y的对立色,则继续为下一个邻接点着色。
若邻接点未着色,则将其染色为y的对立色,之后继续为下一个邻接点着色。
代码实现:
dfs实现染色法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200020; //m条边,则2m个点
int color[N]; //标记每个点的颜色,二分图只需1 & 2两种颜色
int h[N], val[N], ne[N];
int idx;
void add(int x, int y){
val[idx] = y;
ne[idx] = h[x];
h[x] = idx++;
}
int n,m;
bool dfs(int x, int y){
color[x] = y;
for(int i = h[x]; i!=-1; i=ne[i]){
if(color[val[i]] == y){ //检查已着色点是否含有着色冲突
return false;
}else if(!color[val[i]]){ //为未着色点着色
if(!dfs(val[i],3-y))
return false;
}
}
return true;
}
int main(){
memset(h, -1, sizeof(h));
cin >>n >>m;
int a = 0, b = 0;
while(m--){
cin >>a >>b;
add(a, b);
add(b, a);
}
//可能是非连通图,每个连通分量都需要染色
for(int i=1; i<=n; i++){
if(!color[i]){
if (!dfs(i,1)){
cout << "No"<<endl;
return 0;
}
}
}
cout <<"Yes"<<endl;
return 0;
}
bfs实现染色法:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200020;
int h[N], val[N], ne[N];
int idx;
void add(int x, int y){
val[idx] = y;
ne[idx] = h[x];
h[x] = idx++;
}
int n, m;
int color[N];
int tt = 0, hh = 0;
int que[N];
bool bfs(int x, int y){
que[tt++] = x; //从que[0]开始存储
color[x] = y;
while(hh < tt){ //开始hh==tt==0时,队列为空;所以只有hh<tt时队列中含有元素
int t = que[hh++];
for(int i=h[t]; i!=-1; i=ne[i]){
if (color[val[i]] == color[t]){ //检查已着色相邻点是否存在颜色冲突
return 0;
}else if (!color[val[i]]){ //为未着色的点着色
color[val[i]] = 3-color[t];
que[tt++] = val[i];
}
}
}
return 1;
}
int main(){
memset(h, -1, sizeof h);
cin >>n >>m;
int a=0, b=0;
while(m--){
cin >>a >>b;
add(a, b);
add(b, a);
}
//非连通图的每个连通分量都需要染色
for(int i=1; i<=n; i++){
if (!color[i]){
if (!bfs(i, 1)){
cout<< "No";
return 0;
}
}
}
cout<< "Yes";
return 0;
}
代码误区:
1. 两种颜色的转换技巧:
- 假设两种颜色为x色和y色,且记x+y的和为s
这一点染色为x,则下一点染色为s-x
2. 易漏点:非连通图
- 当已经对全图中一点进行染色法,但是仍存在某些点未被着色,说明图是非连通图。
- 对于非连通图的每一个连通分量,选择其中一点进行染色法。
- 每进行一次染色法,都要继续遍历所有点,检查是否还存在未被染色的连通分量。
3. DFS和BFS的相同点:
- 都是在给未染色的点着色时,发现这一点与已染色的相邻点发生了冲突。
本篇感想:
-
染色法本身理解和实现都较为简单,只需记住两点:
给未染色的点染色 & 检查当前点是否和已染色的相邻点发生染色冲突。
-
看完本篇博客,恭喜已登 《筑基境-后期》
距离登仙境不远了,加油