二分图
创建时间:2025-02-18
二分图的判定
二分图的定义如下:
对于无向图 \(G\),若可以将 \(G\) 的节点分为集合 \(S1\) 和 \(S2\),且 \(S1\) 和 \(S2\) 内部没有边,则称 \(G\) 为一个二分图。
形式上来说,设 \(V\) 和 \(E\) 分别为 \(G\) 的点集和边集,若
\[\exist S_1 \subseteq V 与 S_2 = \complement_V S_1, 满足 \forall e(x,y) \in E, (x \in S_1与 y \in S_2) 或 (x \in S_2 与 y \in S_1) \]则称 \(G\) 为一个二分图。
如何判定二分图呢?使用染色法:
从一个未访问的点 \(x\) 出发DFS,若 \(x\) 的邻点 \(y\) 已染色且颜色与 \(x\) 不同,则该图不是二分图;若 \(y\) 未染色,则将 \(y\) 染成与 \(x\) 不同的颜色并从 \(y\) 出发继续DFS。
证明简单,此处省略。
具体实现如下:
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 200;
int n, m, c[MAX_N];
vector<int> e[MAX_N];
bool dfs(int u, int col) {
c[u] = col;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if (c[v] == c[u])
return false;
else if (!c[v]) {
if (!dfs(v, col ^ 1 ^ 2))
return false;
}
}
return true;
}
int main() {
cin >> n >> m;
for (int u, v; m--; ) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
fill(c + 1, c + n + 1, 0);
bool flag = true;
for (int i = 1; i <= n; i++)
if (!c[i])
if (!dfs(i, 1)) {
flag = false;
break;
}
cout << (flag ? "Yes" : "No");
return 0;
}
注:
- 二分图染色为判定型算法,经常与二分答案相结合。
- 二分图存在的充要条件为图中无奇环(点数为奇数的环)。
- 二分图划分方案有 \(2^p\) 中,其中 \(p\) 为图中连通块数量。
二分图最大匹配
对于点集 \(V_1\) 与点集 \(V_2\),选出最多的边使每个点最多与1个点相连(相同点集内的点之间没有边)。
通常使用匈牙利算法求解二分图最大匹配:
(以下将 \(V_1\) 中的点称为左边的点,\(V_2\) 中的点称为右边的点)
遍历每个左边的点 \(x\),从 \(x\) 出发DFS:
若与 \(x\) 相连的右边的点 \(y\) 已匹配上左边的点 \(z\),则尝试让 \(y\) 与 \(x\) 互相匹配,并从 \(z\) 出发DFS,若 \(z\) 可以找到新的匹配点,将答案加1,否则再将 \(y\) 与 \(z\) 连回;若 \(y\) 未与其他点匹配,则直接将 \(x\) 与 \(y\) 匹配,并将答案加1。
具体实现如下:
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 550;
int n1, n2, m;
int match[MAX_N], ans;
vector<int> e[MAX_N];
bool vis[MAX_N];
bool Hungarian(int u) {
vis[u] = true;
for (int v : e[u]) {
if (vis[match[v]])
continue;
if (!match[v] || Hungarian(match[v])) {
match[v] = u;
return true;
}
}
return false;
}
int main() {
cin >> n1 >> n2 >> m;
for (int u, v; m--; ) {
cin >> u >> v;
e[u].push_back(v);
}
for (int i = 1; i <= n1; i++) {
fill(vis + 1, vis + n1 + 1, false);
ans += Hungarian(i);
}
cout << ans;
return 0;
}
注:
- 网络流也能求二分图最大匹配,但很显然,网络流的时间复杂度为 \(O(n^2m)\),而匈牙利算法的复杂度仅为 \(O(nm)\)。
- 二分图最大匹配建模的题常常表现在“无重(chóng)点,求最大”。
二分图最小点覆盖
对于二分图 \(G\),选出最少的点使所有的边都被覆盖。
形式上的说,对于二分图 \(G\),设其点集和边集分别为 \(V\)、\(E\),求出元素数量最少的集合 \(S \subseteq V\) 满足
则称 \(S\) 为 \(G\) 的最小点覆盖。
结论:
令 \(ans\) 为二分图 \(G\) 的最小点覆盖的点数,\(cnt\) 为 \(G\) 的最大匹配数量,则 \(ans=cnt\)。
证明:
令 \(S\) 为最小点覆盖的点集,\(V\) 为最大匹配的点集,\(E\) 为最大匹配的边集,
假设 \(ans < cnt\),则必然 \(\exist e(x,y) \in E, x \notin S 且 y \notin S\),这与二分图最小点覆盖的定义相矛盾,假设不成立。
假设 \(ans > cnt\),则必然 \(\exist x \in S, x \notin V\)。由二分图最大匹配的定义得,对于所有与 \(x\) 相连的点 \(y\),都有 \(y \in V\),否则,可以将 \(x\) 和 \(y\) 匹配,此时 \(x \in V\) 与前文矛盾。故所有与 \(x\) 相连的边都已被覆盖,无需再选 \(x\) 加入 \(S\),这与 \(x \in S\) 的前提矛盾,假设不成立。
综上:\(ans \geq cnt 且 ans \leq cnt\),即 \(ans=cnt\)。
故只需要求出最大匹配数量,便可知最小点覆盖数量。
注:
- 一般图最小点覆盖为NP难题。
- 二分图最小匹配建模的题常常表现在“二选一”的情形
二分图最大独立集
对于二分图 \(G\),选出最多的点使所有的边至少有一端都没被覆盖。
形式上的说,对于二分图 \(G\),设其点集和边集分别为 \(V\)、\(E\),求出元素数量最多的集合 \(S \subseteq V\) 满足
则称 \(S\) 为 \(G\) 的最大独立集。
在最小点覆盖中,任意一条边都被至少选了一个顶点,所以对于其点集的补集,任意一条边都被至多选了一个顶点,所以不存在边连接两个点集中的点,且该点集最大。因此二分图中,最大独立集的点数 = \(n\) - 最小点覆盖的点数。
具体代码实现同最小点覆盖,即最大匹配。
注:
- 一般图最大独立集为NP难题。
- 二分图最大独立集建模的题正如它的名字,体现在“独立”二字
最大团
对于无向图 \(G\),称 \(G\) 的最大团为 \(G\) 点数最多的极大子团(即完全图)。
形式上的说,设 \(G\) 的点集为 \(V\),边集为 \(E\),则 \(G\) 的最大团为元素最多的 \(S \subseteq V\),满足
为了实现最大团,我们引进一个新概念:补图,即生成一张新图,若原图中两点之间无边,则补图中有该边;否则,无该边。
当 \(G\) 的补图为二分时图时,有最大团点数 = 最大独立集点数。故依然可以使用二分图最大匹配解决最大团问题。
注:
- 一般图最大团为NP难题。
最小路径点覆盖
对于有向无环图(即DAG) \(G\),选出最少的路径使得每个点都恰好被一条路径覆盖。
为了求解最小路径点覆盖,有一个很天才的想法:将每个点拆成两个点,一个连接所有以 \(i\) 为起点的所有边,另一个连接以 \(i\) 为终点的所有边。这张新图完全等价于原图,且新图为二分图!
我们可以将所有的出点视为二分图左边的点,入点视为二分图右边的点,并求出新图的最大匹配。显然,原图的最小点覆盖的每条路径的终点在新图中的出点必然不在最大匹配中(若在最大匹配中,则可以顺着其入点继续往下走,该点一定不为路径终点),所以最小点覆盖的路径数 = 不在最大匹配中的点的数量(只算二分图的一边),即路径数 = \(n\) - 最大匹配点数。
故可以用二分图最大匹配求出最小路径点覆盖的路径数,又因为在求出最大匹配的同时我们得到了右边的点 \(match\) 数组,根据 \(match\) 数组,我们使用 \(matched\) 数组记录左边的每个点是否已匹配,若 \(mathced_i=false\),即 \(i\) 的出点不在二分图最大匹配中,可以从 \(i\) 出发DFS,倒序寻找 \(i\) 所在的这条路径:显然,\(match_i\) 即 \(i\) 的入点所匹配的点为这条路径中 \(i\) 的上一个点,当 \(match_i = 0\) 时,\(i\) 为路径起点,结束DFS。
就这样,我们打印出了二分图最小点覆盖的方案,代码如下:
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 200;
int n, m, ans;
vector<int> e[MAX_N];
int match[MAX_N];
bool vis[MAX_N];
bool matched[MAX_N];
bool Hungarian(int u) {
vis[u] = true;
for (int v : e[u]) {
if (vis[match[v]])
continue;
if (match[v] == 0 || Hungarian(match[v])) {
match[v] = u;
matched[u] = true;
return true;
}
}
return false;
}
void print_path(int u) {
if (match[u] != 0)
print_path(match[u]);
cout << u << ' ';
}
int main() {
cin >> n >> m;
for (int u, v; m--; ) {
cin >> u >> v;
e[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
fill(vis + 1, vis + n + 1, false);
ans += Hungarian(i);
}
for (int i = 1; i <= n; i++)
if (!matched[i]) {
print_path(i);
cout << '\n';
}
cout << n - ans;
return 0;
}
二分图的应用
洛谷P1525(二分图判定+二分答案);
CF85E(二分图划分方案数);
洛谷P1155(二分图判定建模);
洛谷P10937(二分图最大匹配建模);
洛谷P2071(二分图最大匹配建模);
洛谷P2756(二分图最大匹配+输出方案);
洛谷P2055(二分图最大匹配建模);
洛谷P1129(二分图最大匹配建模);
UVA1194(二分图最小点覆盖建模);
洛谷P7368(二分图最小点覆盖建模);
洛谷P6062(二分图最小点覆盖建模+连通块);
洛谷P7086(二分图最小点覆盖+输出方案);
洛谷P3355(二分图最大独立集);
洛谷P5030(二分图最大独立集建模);
洛谷P2423(最大团建模);
洛谷P2764(最小路径点覆盖+输出方案);
洛谷P10938(最小点覆盖+传递闭包)。

浙公网安备 33010602011771号