二分图
染色法(判断是不是二分图)
时间复杂度: O ( n + m ) O(n+m) O(n+m)
什么是二分图
可以把所有的点分成两边,使得所有的边都是在集合之间的(集合内部没有边)
二分图的性质
一个图是二分图,当且仅当图中不含奇数环,等价于用染色法染色不存在矛盾(奇数环:环当中边的数量是奇数)
![image-20210108112312938](https://raw.githubusercontent.com/QiuHong-1202/FigureBed/main/image-20210108112312938.png)
必要性证明: 给定一个奇数环,可以推出一个点既在左边,又在右边
充分性证明:从前往后遍历所有点,和黑色相邻的点是白色和白色相邻的点是黑色—由于图中不含奇数环,所以染色过程中一定没有矛盾(反证法:假设在染色过程中出现了矛盾,
染色法
-
f
o
r
i
:
1
→
n
for\ i:1\to n
for i:1→n
-
i
f
v
if\ v
if v 未染色
- d f s ( i ) dfs(i) dfs(i)
-
i
f
v
if\ v
if v 未染色
Code
给定一个 n n n 个点 m m m 条边的无向图,图中可能存在重边和自环。
请你判断这个图是否是二分图。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int N = 1e6 + 7, M = N * 2;
int h[N], e[M], ne[M], idx;
int color[M];
int n, m;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int u, int c) {
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!color[j]) {
if (!dfs(j, 3 - c)) return false; //如果当前是1,变为2,如果当前是2 变为1
} else if (color[j] == c) return false;
}
return true;
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof(h));
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
int flag = true; //标记是否出现了矛盾
for (int i = 1; i <= n; i++) {
if (!color[i]) {
if (!dfs(i, 1)) {
flag = false;
break;
}
}
}
if (flag) puts("Yes");
else puts("No");
return 0;
}
匈牙利算法 (求二分图最大匹配)
最大匹配:边数最多的一组匹配,等价于不存在增广路径
匹配点:在匹配当中的点
非匹配点:不在匹配当中的点
增广路径:从一个非匹配点走,沿着非匹配边,走到一个匹配点,再沿着匹配边走,再沿着非匹配边走…(开始的左边是非匹配点,右边是非匹配点)
![image-20210310160006941](https://i-blog.csdnimg.cn/blog_migrate/7c19d9ad837fe8d064b4c6bc7660ceb3.png)
时间复杂度: O ( n m ) O(nm) O(nm),实际远小于 O ( n m ) O(nm) O(nm)
在一个比较快的时间内求出左边和右边匹配成功的最大数量(没有两条边是共用了一个点的)
基本思路
- 对左半边的点进行匹配,如果左半边点有链接右半边点的路径,把他们相连
- 如果一个点的出边已经被其他点相连,询问这个已经被连的点,看看它连它的点是不是可以连其他点。尝试所有可能之后,都不能联通,才放弃。
Code
给定一个二分图,其中左半部包含 n 1 n_1 n1 个点(编号 1 ∼ n 1 1\sim n_1 1∼n1),右半部包含 n 2 n_2 n2 个点(编号 1 ∼ n 2 1\sim n_2 1∼n2),二分图共包含 m m m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 7, M = N * 2;
int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N];
bool st[510];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool find(int x) {
for (int i = h[x]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) {
st[j] = true;
if (match[j] == 0 || find(match[j])) {
match[j] = x;
return true;
}
}
}
return false;
}
int main() {
cin >> n1 >> n2 >> m;
memset(h, -1, sizeof(h));
int u, v;
for (int i = 1; i <= m; i++) {
cin >> u >> v;
add(u, v);
}
int ans = 0;
for (int i = 1; i <= n1; i++) {
memset(st, false, sizeof(st));
if (find(i)) ans++;
}
cout << ans;
return 0;
}
习题
覆盖问题
结论
最大匹配数 = 最小点覆盖 = 总点数 - 最大独立集 = 总点数 - 最小路径点覆盖
最小点覆盖
定义:给定一个图,从中选出最少的点,使得每条边的两个端点中至少有一个是被选出来的
![image-20210310171207807](https://i-blog.csdnimg.cn/blog_migrate/197fe4d804a5c3853e4781504ad7fcd2.png)
在二分图中,最小点覆盖等于最大匹配数。
证明
- 最小点覆盖 ≥ \ge ≥ 最大匹配数
在二分图的一组匹配中,匹配边是没有公共点的,如果最大匹配数为 m m m ,需要覆盖所有边,最小的覆盖点一定要大于 m m m
- 最小点覆盖 = = = 最大匹配数 的等号可以成立
最大独立集
最大独立集:从一个图当中选出最多的点,使得选出的点之间是没有边的(但是可以和独立集之外的点连边),点数最多的集合是最大独立集
最大团:从一个图中选出最多的点,使得任意两点之间都有边
补图:把之前有连边的点删掉边,把没有连边的点连上边,这样就求得了一个图的补图
注意:设原图为 G G G , G G G 的补图为 G ′ G' G′ ,原图的最大独立集就是补图的最大团。
在二分图中,如果要求最大独立集,等价于选出最多的点,使得选出的点之间是没有边的,等价于去掉最少的点,破坏所有边,等价于找到最少的点,覆盖所有的边(最小点覆盖),等价于找最大匹配
设去掉 m m m 个点,总共有 n n n 个点,最大独立集就是 n − m n-m n−m
注意:在普通图中,找最大独立集是一个 N P − h a r d NP-hard NP−hard 问题,没有多项式时间的解
最小路径点覆盖(最小路径重复点覆盖)
也被称为最小路径覆盖,针对一个有向无环图 ( D A G DAG DAG),希望用最少的互不相交(点不重复)的路径将所有点覆盖住
结论:最小路径点覆盖 = 总点数 - 最大匹配数
- 建图
求最小路径覆盖用到拆点,把一个点分成两个点,分别表示出点和入点,那么从点
i
→
j
i\to j
i→j 的一条边就用,从左边的出点
i
i
i 连到右边的入点
j
’
j’
j’ 表示,
于是得到的图是一个二分图,因为所有的边都是在左部和右部之间的,内部没有点
- 转化
此时将原图中的每一条路径转化到新图中,因为原图中的路径互不相交,所以每一个点最多只有一个出度和入度
这就意味着,在新图中,左部每一个点最多只会向右部连一条边,右部的点最多只会有一条边连入,每个点最多只会属于一条边。
- 原图中的一条路径 ⇔ \Leftrightarrow ⇔ 新图中的一组匹配(新图中每一个点最多只会属于一条边)
- 原图中每一条路径的终点(没有出边) ⇔ \Leftrightarrow ⇔ 新图左部的非匹配点
- 推导
求原图中互不相交路径数 ⇔ \Leftrightarrow ⇔求路径终点数最少 ⇔ \Leftrightarrow ⇔求左部非匹配点最少 ⇔ \Leftrightarrow ⇔求最大匹配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KwVZXplF-1615859122578)(https://i.loli.net/2021/03/15/RDa5yYXtUqxV6mn.png)]
最小路径重复点覆盖
针对一个有向无环图 (
D
A
G
DAG
DAG),希望用最少的互不相交(点不重复)的路径将所有点覆盖住。即,在最小路径覆盖问题的基础上,去掉互不相交。
- 设原图为 G G G 求传递闭包 G ′ G' G′ ,在 G ′ G' G′ 上求最小路径覆盖
- 原图的最小路径重复点覆盖就等于新图的最小路径覆盖
可以用二分图但是效果不好
最优匹配 (KM)建议最小费用流
多重匹配 (每个点可以匹配多个点)建议最大流