二分图学习笔记
前言
什么是二分图?二分图,又称二部图,如下图,是一类结构特殊的图。它的顶点集可以划分为两个互不相交的子集,使得图中的每条边都连接这两个集合之间的一对点,而不会连接同一集合内部的点,简单讲就是一个图的节点被分为两个集合,每个集合只向另一个集合建边的图,这样的图有很多性质,本篇着重讲述二分图染色法判断,二分图最大匹配(匈牙利算法),二分图最大独立集

树、偶环、网格图等都是常见的二分图的例子
染色法二分图
首先染色法,顾名思义,我们把每个节点染上颜色,通过dfs或bfs,从一个节点开始将当前节点染成黑色,递归到第二个节点,将节点染成白色,再将第三个节点再染成黑色即可,如果递归过程中出现了该节点的下一个节点被上过颜色,且与当前节点要上的颜色相同,那就产生了奇数环(一个环中节点的个数为奇数),有奇环就不是二分图,这就是二分图判定
【参考代码】
// 只展示dfs做法
inline bool find(int u, int c){
col[u] = c;
for(register int j : g[u]){
if(col[j] == c){
return false;
}
else if(col[j] == 0 && !find(j, c ^ 2)){
return false;
}
}
return true;
}
P1525 [NOIP 2010 提高组] 关押罪犯
二分答案,染色法
思路
首先我们可以想到二分答案,二分取得最小的怨气。根据每个人之间的怨气,大于mid的就不能放在一个监狱里,就建边。如果最终的图是个二分图,证明可行,如果不是,则证明不可行。如此二分出最终答案即可
【参考代码】
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m;
int col[N];
vector<int> g[N];
struct Edges{
int a, b, c;
}e[N];
inline bool find(int u, int c){
col[u] = c;
for(register int j : g[u]){
if(col[j] == c){
return false;
}
else if(col[j] == 0 && !find(j, c ^ 2)){
return false;
}
}
return true;
}
inline bool check(int mid){
for(register int i = 1 ; i <= n ; i ++){
g[i].clear();
col[i] = 0;
}
for(register int i = 1 ; i <= m ; i ++){
register int a = e[i].a, b = e[i].b, c = e[i].c;
if(c <= mid) continue;
g[a].push_back(b);
g[b].push_back(a);
}
for(register int i = 1 ; i <= n ; i ++){
if(!col[i] && !find(i, 1)) return false;
}
return true;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for(register int i = 1 ; i <= m ; i ++){
register int a, b, c;
cin>>a>>b>>c;
e[i] = {a, b, c};
}
int l = 0, r = 1e9;
while(l < r){
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout<<r;
return 0;
}
二分图最大匹配
一个重要的小性质:最小点覆盖=最大匹配数(konig定理)
二分图最大匹配可以看做是男生女生配对,左边是男生,右边是女生,假设每个男生都有自己喜欢的女孩(可以不止一个),我们希望让每一个男孩子都找到喜欢的对象,也就是让匹配对数更多,如何处理呢?
匈牙利算法
增广路 :从一个未匹配的点出发,走交错路,到达了一个未匹配过的点,这条路叫做增广路。
为了求出最大匹配,算法依次枚举所有顶点,求出从它出发的一条增广路,每当找到一条增广路,就将路径上路径匹配状态都取反,这样就可以找到一个更大的匹配。因为增广路的长度总是奇数,所以在二分图中,它的端点必然分别位于左右两个部分。这说明,只需要考虑从左部出发的增广路即可
这是匈牙利算法的演示动图

代码如下
bool vis[N];
int match[N];
inline bool find(int u){
for(register int j : e[u]){
if(!vis[j]){
vis[j] = true;
if(!match[j] || find(match[j])){
match[j] = u;
return true;
}
}
}
return false;
}
P3386【模板】二分图最大匹配
思路
模板题没什么说法,就是建边跑个二分图匹配就好了
【参考代码】
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n1, n2, m;
vector<int> e[N];
bool st[N];
int match[N];
bool find(int u){
for(int j : e[u]){
if(!st[j]){
st[j] = true;
if(!match[j] || find(match[j])){
match[j] = u;
return true;
}
}
}
return false;
}
int main(){
cin>>n1>>n2>>m;
while(m --){
int a, b;
cin>>a>>b;
e[a].push_back(b);
}
int ans = 0;
for(int i = 1 ; i <= n1 ; i ++){
memset(st, 0, sizeof st);
if(find(i)) ans ++;
}
cout<<ans;
return 0;
}
P2825 [HEOI2016/TJOI2016] 游戏 & 放置机器人
思路
如果这个棋盘上只有空地,每行列只能放一个炸弹,那么我们很容易想到将行列设为二分图的左右点集,每个行向可以放置列建边,不可放置的位置就是石头,软石头很好处理,只需在建边的时候判掉就行了,硬石头的处理就需要拆点,行和列分开处理,每当遇到硬石头就将行和列迭代一位,再将迭代的行和列处理,最后跑最大匹配数边的个数即可
【参考代码】
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, ntot, mtot;
char s[N][N];
PII last;
int row[N][N], col[N][N];
vector<int> e[N];
bool vis[N];
int match[N];
inline bool find(int u){
for(register int j : e[u]){
if(!vis[j]){
vis[j] = true;
if(!match[j] || find(match[j])){
match[j] = u;
return true;
}
}
}
return false;
}
int main(){
cin>>n>>m;
bool flag = true;
for(register int i = 1 ; i <= n ; i ++){
for(register int j = 1 ; j <= m ; j ++){
cin>>s[i][j];
if(s[i][j] == '#'){
flag = true;
continue;
}
if(s[i][j] == 'x') continue;
if(!flag){
row[i][j] = row[last.first][last.second];
last = {i, j};
}
else if(flag){
row[i][j] = row[last.first][last.second] + 1;
last = {i, j};
ntot ++;
flag = false;
}
}
flag = true;
}
flag = 1;
last = {0, 0};
for(register int i = 1 ; i <= m ; i ++){
for(register int j = 1 ; j <= n ; j ++){
if(s[j][i] == '#'){
flag = 1;
continue;
}
if(s[j][i] == 'x') continue;
if(!flag){
col[j][i] = col[last.second][last.first];
last = {i, j};
}
else if(flag){
col[j][i] = col[last.second][last.first] + 1;
last = {i, j};
mtot ++;
flag = false;
}
}
flag = true;
}
for(register int i = 1 ; i <= n ; i ++){
for(register int j = 1 ; j <= m ; j ++){
if(s[i][j] == '*'){
register int a = row[i][j], b = col[i][j];
e[a].push_back(b);
}
}
}
int ans = 0;
for(register int i = 1 ; i <= ntot ; i ++){
memset(vis, 0, sizeof vis);
if(find(i)){
ans ++;
}
}
cout<<ans;
return 0;
}
二分图-最小点覆盖
最小点覆盖问题指的是,在一个图中,每条边两边的节点至少有一个被选
我们在这里只研究在二分图中的最小点覆盖,一般的最小点覆盖问题难度是NP级的,但是在二分图中,我们有一个非常神奇的定理:
Kőnig 定理
二分图中,最小点覆盖中的顶点数量等于最大匹配中的边数量
二分图-最大独立集
对于最大独立集,难度是NP级的,但二分图的最大独立集,也有定理和推论
定理 推论
图 \(G=(V,E)\) 中,点集 \(C\subseteq V\) 是点覆盖,当且仅当它的补集 \(V\setminus C\) 是独立集
图 \(G=(V,E)\) 中,最小点覆盖与最大独立集的大小之和等于顶点数目
贴点题
-
染色法判断二分图
-
二分图-最大匹配

浙公网安备 33010602011771号