二分图

概论

二分图又称作二部图,是图论中的一种特殊模型。 设G = (V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(u,v)所关联的两个顶点u和v分别属于这两个不同的顶点集(u->A, v->B),则称图G为一个二分图。

染色法判定二分图

思路

初始将所有节点标记为0,表示未染色;
选取节点u将其染色为1,然后遍历与u相邻的节点v,如果未染色,将其染为3 - 1 = 2,如果染的颜色为1,那么就直接返回False,表示不是二分图。注意,我们需要遍历所有的点,然后染色,如果之前的遍历中染过,那么就无需再染色,否则需要染色,这样做的原因是,可能有多个孤立点独立的点集
bfs较好,防止dfs爆栈,这一点Python多有体会!

当然使用并查集也是可以的,对于节点u,将与它相邻的节点v合并到一个集合!

image

代码

def bfs():
    n = len(g)
	color = [0] * n
    for i in range(n):
        if color[i]:
			continue
        color[i] = 1
        q = deque([i])
        while q:
            u = q.popleft()
            for v in g[u]:
                if color[v] == 0:
                    color[v] = 3 - color[u]
                    q.append(v)
                elif color[v] == color[u]:
                    return False
    return True

题目链接
https://atcoder.jp/contests/abc327/tasks/abc327_d
题目代码

#include<bits/stdc++.h>
using namespace std;

int n,m,t = 1;
void solve()
{
	cin >> n >> m;
	vector<vector<int>>g(n + 1);
	vector<int> a(m + 1),b(m + 1);
	for(int i = 1;i <= m;++i) cin >> a[i];
	for(int i = 1;i <= m;++i) cin >> b[i];
	for(int i = 1;i <= m;++i){
		g[a[i]].push_back(b[i]);
		g[b[i]].push_back(a[i]); 
	}
	vector<int> color(n + 1,0);
	for(int i = 1;i <= n;++i){
		if(color[i]) continue;
		color[i] = 1;
		queue<int> q;
		q.push(i);
		while(!q.empty()){
			int u = q.front();q.pop();
			for(int v:g[u]){
				if(color[v] == 0){
					color[v] = 3 - color[u];
					q.push(v);
				}else if(color[v] == color[u]){
					cout << "No" << '\n';
					return;
				}
			}
		}
	}
	cout << "Yes" << '\n';
}

int main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//	cin >> t;
	for(int i = 0;i < t;++i) solve();
	return 0;
}

匈牙利算法求二分图的最大匹配

思路

先到先得,能让则让!
时间复杂度:O(mn)

image

代码

# 匈牙利算法求二分图的最大匹配
from collections import defaultdict
import sys
input = sys.stdin.readline
read = lambda:map(int,input().split())
n1,n2,m = read()
match = [False] * (n2 + 1)
g = defaultdict(list)
for _ in range(m):
    u,v = read()
    g[u].append(v)
def dfs(u):
    for v in g[u]:
        if not vis[v]:
            vis[v] = True
            # 先到先得,能让则让!
            if not match[v] or dfs(match[v]):
                match[v] = u
                return True
    return False
res = 0
for u in range(1,n1 + 1):
    vis = defaultdict(lambda:False)
    if dfs(u):
        res += 1
print(res)

例题突破

例题1

https://www.luogu.com.cn/problem/P1129
image

思路

将每一行的行号与该行黑色块所在的列号相连,加入二分图两边的集合!
任意两行或者任意两列之间的交换都不会破坏他们的平衡性。
如果最大匹配数为n,则表明有解!

例题2

image

思路

按下凸起部分,相当于删除该凸起点所在的行或列,直到所有的凸起部分都被删除!
我们可以将建立凸起点的行号与列号的匹配,求最小点覆盖集,也就是最大匹配数!
也就是删除最少的点,使得所有边都被删除!

例题3

image

思路

礼貌拿图【https://zhuanlan.zhihu.com/p/96229700】
image
将每一个点与周围上下左右四个点建立匹配边,求二分图的最大匹配即可!
注:要进行坐标的映射,将(x,y) -> x * (col - 1) + y

术语

最小点覆盖:找到最少的一些点,使二分图所有的边都至少有一个端点在这些点之中。
一个二分图中的最大匹配数等于这个图中的最小点覆盖数

posted @ 2024-04-09 09:44  gebeng  阅读(33)  评论(0)    收藏  举报