本文参考于:https://www.cnblogs.com/czsharecode/p/9777533.html
一、二分图的基本概念
二分图又称为二部图,是图论的一种特殊模型。
设 G=(V,E)是一个无向图,如果顶点 V 可以分割为两个互不相交的子集 (A,B),并且图中的每条边 (i,j) 所关联的顶点 i 和 j 分别属于这两个不同的顶点集 (i in A,j in B) ,则称图 G 为一个二分图。
也就是说,只要这两个点之间有边,那么这两个点就不能同属一个集合,必须分在两边。
这也就带来一个问题,并不是所有的无向图 G 都能转化为二分图。
二、二分图的判定
定理:无向图 G 为二分图的充要条件是,G 至少有两个顶点,且其所有回路的长度均为偶数。
判定方法:根据定义比根据定理更好判断,只需要从第一个点开始,将其染成颜色1,则与其所有相连的所有顶点都染成颜色2,如果与其相连的点已经被染成颜色1,则图G不是二分图。
模板题目:https://www.acwing.com/problem/content/862/
把该点的所有终点当作新的起点,重复以上过程。很显然,既可以用 dfs 染色,有可以用 dfs 染色。
vector<int> G[maxn]; //图
int n,m; //顶点数
int col[maxn]; //顶点的颜色 1|-1
//把顶点染成1或者-1
bool DFS(int v,int c)
{
col[v] = c; //把顶点v染成颜色c
for(int i=0;i<G[v].size();i++){
int to = G[v][i];
//如果相邻顶点同色,则返回false
if(col[to] == c) return false;
//如果相邻顶点没有染色,则染成 -c
if(col[to]==0&&!DFS(to,-c)) return false;
}
//如果所有顶点都染过色,就返回true
return true;
}
bool solve()
{
for(int i=1;i<=n;i++){
if(col[i]==0){
//如果顶点i还没被染色,则染成 1
if(!DFS(i,1)){
return false;
}
}
}
return true;
}
bfs染色代码
int n,m;
vector<int> G[maxn];
int col[maxn] ;
bool bfs(int s)
{
col[s] = 1;
queue<int> que;
que.push(s);
while(!que.empty()){
int from = que.front();
que.pop();
for(int i=0;i<G[from].size();i++){
int to = G[from][i];
if(to==from) continue;
// 如果相邻的点没有上色就给这个点上色
if(col[to]==0){
que.push(to);
col[to] = -col[from];
}
// 如果相邻的颜色相同则返回false
if(col[to]==col[from])
return false;
}
}
// 如果所有的点都被染过色,且相邻的点颜色都不一样,返回true
return true;
}
bool solve(){
for(int i=1; i<=n; i++){
if(col[i] == 0)
if(!bfs(i)){
return false;
}
}
return true;
}
二分图的最大匹配
定义:在一个无向图中,定义一条边覆盖的点为这条边的两个端点。
找到一条边集 S 包含最多的边,使得这个边覆盖到所有顶点中的每一个点只被一条边覆盖。 S 的大小叫做图的最大匹配。
通俗地讲,比如说有一场宴会,男孩和女孩跳舞,并且他们必须相互喜欢才能在一起跳舞,一个男孩可能喜欢 0 个或多个女孩,一个女孩也可能喜欢 0 个或多个男孩,但一个男号和他喜欢的 女孩跳舞之后就不能和其它他喜欢的女孩跳舞,女孩也是如此。请问最多可以多少对男孩女孩一起跳舞。
很显然,互相喜欢的男孩女孩建边得到一个二分图,求一个一个边集 S 包含最多的边,使得这个边覆盖到所有顶点中的每个顶点只被一条边覆盖,即求二分图的最大匹配。
[匈牙利算法]
把二分图分为 A,B 两个集合,依次枚举 A 中的每个点,尝试在 B 集合中找到一个匹配。
对于一个 A 集合中一个 x,假设 B 集合有一个与其相连的点 y,若 y 暂时还没有匹配点,那么 x 可以和 y 匹配,找到;
否则,设 y 已经匹配到点 z,(显然 z 是 A 集合中的一个点),那么,我们将尝试为 z 找一个除了 y之外的匹配点,若找到,那么 x 可以和 y 匹配,否则 x 和 y 不匹配。
【图解】
我们以下图为例说明匈牙利匹配算法
st1:从 1 开始,找到右侧的点 4,发现点 4没有被匹配。得到下图:
st2:接下来,从 2 开始,试图在右边找到一个它的匹配点,我们枚举 5,发现 5 还没有被匹配,于是找到了 2 的匹配点为 5,得到如下图:
st3:接下来,我们找 3 的匹配点。我们枚举了 5,发现 5 已经有了匹配点 2。此时,我们试图找到 2 除 5 以外的另一个匹配点,我们发现,我们可以找到 7,于是 2 可以匹配到 7,所以 5 可以匹配给 3,得到下图:
此时,结束,我们得到最大匹配为 3。
【算法核心】
每次寻找可以匹配 A 点的一个点 B,如果这个点 B 还没有被匹配,暂时就把这个点 B 当作 A 的匹配点;如果这个 B 在之前已经匹配了 C,那就看 C 能不能匹配除了 B 以外的未匹配点,如果找不到则重复以上过程直到找到或者枚举所有可能点还找不到,结束点A的匹配;如果找到,则把 B 匀给 A(本来 B 是 C 的,现在 C 说我还有其他舞伴,B 就让给 A 了)。这样就能多出一个匹配,相当于找到一条“增广路径”。
【复杂度】
时间复杂度:邻接矩阵:最坏为O(n3),邻接表:O(nm)
空间复杂度 :邻接矩阵:O(n2),邻接表:O(n+m)
int G[maxn][maxn],mh[maxn];
bool vis[maxn];
int m,n; // m代表图左边一列,n代表图右边一列
bool dfs(int x)
{
for(int y=1;y<=n;y++){
if(G[x][y]==0) continue;
if(!vis[y]){
vis[y] = true;
if(!mh[y]||dfs(mh[y])){
mh[y] = x;
return true;
}
}
}
return false;
}
int ans = 0;
for(int i=1;i<=m;i++){
memset(vis,false,sizeof(vis));
if(dfs(i)) ans++;
}
POJ 1469 COURSES
已知,有N个学生和P门课程,每个学生可以选0门,1门或者多门课程,要求在N个学生中选出P个学生使得这P个学生与P门课程一一对应。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<ctime>
#include<map>
#define ll long long
#define ld long double
#define ull unsigned long long
using namespace std;
const int maxn = 510;
int G[maxn][maxn];
int match[maxn];
bool vis[maxn];
int n,m;
bool dfs(int x)
{
for(int y=1;y<=m;y++){
if(G[x][y]==0) continue;
if(!vis[y]){
vis[y] = true;
if(!match[y]||dfs(match[y])){
match[y] = x;
return true;
}
}
}
return false;
}
int main(void)
{
int t,p;
scanf("%d",&t);
for(int k=1;k<=t;k++){
scanf("%d%d",&m,&n);
memset(G,0,sizeof(G));
memset(match,0,sizeof(match));
int x;
for(int y=1;y<=m;y++){
scanf("%d",&p);
while(p--){
scanf("%d",&x);
G[x][y] = 1;
}
}
int ans = 0;
for(int i=1;i<=n;i++){
memset(vis,false,sizeof(vis));
if(dfs(i)) ans++;
}
if(ans==m) printf("YES\n");
else printf("NO\n");
}
return 0;
}
二分图的最小顶点覆盖
定义:假设选了一个点就相当于覆盖了以它为端点的所有边。最小顶点覆盖就是个选择最少的点来覆盖所有的边。
定义:最小顶点覆盖等于二分图的最大匹配。
二分图的最大独立集
定义:选出一些顶点使得这些顶点两两不相邻,则这些点构成的集合称为独立集。找出一个包含最多的独立集称为最大独立集。
定理:最大独立集 = 所有顶点数 - 最小顶点覆盖 = 所有顶点数 - 最大匹配
【模板】
建边是相反意义建边,把有冲突关系的两个点建边,再套用最大匹配的模板,用所有顶点数减之即可。