二分图知识点杂记

这篇文章记录了我最近学到的二分图知识点,主要是帮助我自己复习。大家也可以参考一下。

二分图简介

二分图标准介绍(来自 OI-wiki)

不要紧,看不懂标准的数学语言,可以概括一下。

其实,二分图就是一张图。如果我们把它的顶点分为两类,那么满足同类顶点之间没有边相连

二分图有很多作用,这里刚学也体现不出来,后面我会补充的!

二分图的判定

判断一个图是否为二分图通常使用染色法。
染色法的原理是:如果一张图中存在奇环,那么此图一定不是二分图。因为只要出现奇环,就会发现有一条边连接了同一个集合中的两个顶点,是不符合二分图的定义的。因此,我们可以借这个原理,来判断一张图是否为二分图。
我这里来介绍一下它的算法流程:

首先,准备一个颜色数组记录每个顶点的颜色,初始化颜色为 \(0\)

  1. 找到一个没有被染色的顶点,尝试将其染色。
  2. 不断找与它有边相连的顶点,若未染色,就将其染为与当前顶点相反的颜色。
  3. 如果相邻顶点已经染色,那么判断:若相邻顶点与当前顶点的颜色相同,则此图不是二分图,退出循环。
  4. 否则不断重复以上操作,直到每个顶点都被染色。

这就是二分图的判定方法,通常用 DFS 或 BFS 算法实现。这里,我给出了 DFS 算法的程序:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
int color[1010];
vector<int> g[1010];
bool dfs(int u, int c)//u:当前顶点;c:当前颜色 
{
	color[u] = c;//标记颜色 
	for(int i = 0;i < g[u].size();i ++)
	{
		int v = g[u][i];//目标顶点 
		if(!color[v])//没染过色 
			if(dfs(v, 3 - c))//如果出现冲突 
				return 1;
		if(color[v] == c)//出现冲突 
			return 1;
	}
	return 0;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1, u, v;i <= m;i ++)
	{
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	bool fg = 0;
	for(int i = 1;i <= n;i ++)
	{
		if(!color[i])//没染过色 
			if(dfs(i, 1))//如果出现了冲突 
			{
				fg = 1;
				break;
			}
	}
	if(fg)
		cout << "No" << endl;
	else
		cout << "Yes" << endl;
	return 0;
}

【模版】二分图最大匹配

二分图最大匹配总介绍(来自 OI-wiki)

简化题意:设一个图是二分图。若在它的一个子图中,任意两条边都没有公共顶点,那么称此子图是二分图的一组匹配。在二分图中,包含边数最多的一组匹配称为二分图的最大匹配

1、匈牙利算法

首先,我们设已经匹配的边叫做匹配边,没有匹配的边叫做非匹配边;已经和另一个节点匹配过的节点叫做匹配点;没有匹配过的节点叫做未匹配点

然后,我们引入两个概念:

  • 交替路:从一个未匹配点出发,依次走过非匹配边、匹配边、非匹配边……形成的路径,叫做交替路。
  • 增广路:从一个未匹配点出发,走交替路。若能到达另外一个未匹配点,那么这条交替路称为增广路。

观察增广路,我们会发现:对于一条增广路,非匹配边一定比匹配边多一条
而如果我们把非匹配边与匹配边交换,匹配边也会比原来多一条。那么,增广路就可以增加一组匹配

匈牙利算法的核心思想就是不断的找图中的增广路,直到找不到增广路时,此时就能达到最大匹配

这里,我给出了 DFS 的参考代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m, e, ans = 0;
vector<int> g[510];
int match[510];
bool vis[510];
bool dfs(int u)
{
	for(int i = 0;i < g[u].size();i ++)
	{
		int v = g[u][i];
		if(vis[v])// 已经到过了 
			continue;
		vis[v] = 1;
		if(!match[v] || dfs(match[v]))// 如果节点 v 还未匹配,或者可以把这个匹配通过调整让给节点 u 
		{
			match[v] = u;// 标注 v 节点与 u 节点匹配 
			return true;// 返回:可以增加一组匹配 
		}
	}
	return false;// 不可以再增加匹配 
}
int main()
{
	scanf("%d %d %d", &n, &m, &e);
	for(int i = 0, u, v;i < e;i ++)
	{
		scanf("%d %d", &u, &v);
		g[u].push_back(v);
	}
	for(int i = 1;i <= n;i ++)
	{
		memset(vis, 0, sizeof(vis));
		if(dfs(i))// 如果可以匹配完成,就增加答案 
			ans ++;
	}
	printf("%d\n", ans);
	return 0;
}

上面的代码的主要思想就是:
首先,\(\text{dfs}(u)\)\(1 \leq u \leq n\))代表为左部节点 \(u\) 匹配节点的过程。
我们枚举节点 \(u\) 所有的可匹配对象,设第 \(i\) 个可匹配对象为 \(v_i\)
那么有这些情况:

  • \(vis_i = 1\),说明已经尝试过匹配节点 \(v_i\) 了,继续尝试下一个可匹配节点。
  • \(vis_i = 0\),尝试匹配节点 \(v_i\)
    • \(match_{v_i} = 0\),说明还没有别的节点与 \(v_i\) 尝试匹配过。那么使 \(match_{v_i} = u\),返回找到匹配方式。
    • \(match_{v_i} \neq 0\),说明已经有别的节点与 \(v_i\) 尝试匹配过。
      • \(dfs(match_{v_i}) = 1\),说明可以调整匹配方案将节点 \(v_i\) 空出来给节点 \(u\) 匹配。那么使 \(match_{v_i} = u\),返回找到匹配方式。
      • \(dfs(match_{v_i}) = 0\),说明不可以调整匹配方案将节点 \(v_i\) 空出来给节点 \(u\) 匹配。那么尝试下一个可匹配节点。
  • 如果经过上面的循环没有任何匹配方式,返回没有匹配方式。

最后,如果 \(\text{dfs}(u) = 1\)\(1 \leq u \leq n\)),说明可以再增加一组匹配,则答案数量增加。

最后,输出答案数量,即为二分图的最大匹配数量。

二分图实际运用例题

在二分图的实际运用中,发现二分图算法与构图(目前对于我来说)是比较重要与困难的步骤。因此,希望通过记录的方式来巩固。

P1894 [USACO4.2] 完美的牛栏The Perfect Stall

点击查看思路提示
这题构图很简单,主要就是二分图最大匹配的模版套用,适合入门。

题解:点这里

P1640 [SCOI2010] 连续攻击游戏

点击查看思路提示
这题主要考查如何构图,与一点优化。作为入门题还是比较适合的。

题解:点这里

posted on 2026-02-22 22:07  Leo_29  阅读(9)  评论(0)    收藏  举报

导航