笔记:二分图

概念

  • 二分图:又称作二部图,设 G=(V,E) 是一个无向图,如果顶点集 V 可分割为两个互不相交的子集 A,B,并且图中的每条边 (u,v) 所关联的两个顶点 u,v 分别属于这两个顶点集 (uA,vB),则称图 G 为一个二分图。也就是说一个图被划分成了两个不相交的集合,集合内部没有边相连。
  • 匹配:在图 G=(V,E) 中,边集 EE 被称为 G 的⼀一个匹配当 且仅当对于 V 中的每个点,E 中与其关联的边不超过一条。
  • 最大匹配:二分图中匹配的边数最多的匹配。但最大匹配的边数是确定的,而且不可能超过图中定点数的一半。
  • 极大匹配:无法再增加匹配边的匹配。不一定是最大匹配
  • 最大权匹配:加权图中,权值和最大的匹配。
  • 最大权最大匹配:匹配数最多的前提下,边权和最大的匹配。即所有最大匹配中,边权和最大的匹配。
  • 完美匹配:二分图中所有的点都为匹配点的匹配。
  • 交错路:始于非匹配点且由匹配边与非匹配边交错而成。
  • 增广路:始于非匹配点且终于非匹配点(除了起始的点)的交错路。增广路中边的数量是奇数。
    • 增广路上非匹配边比匹配边数量多 1,如果将增广路上的匹配边和未匹配边反转,则匹配数量会增加 1 且依然是交错路。
    • 根据增广路定理,当找不到增广路的时候,得到最大匹配。
    • 简证一下:因为增广操作每次只会使得两端的未匹配点变成匹配点,不会出现增广后有匹配点变成未匹配点的情况,所以每个点作为未匹配点只需要增广一次。
  • 二分图最小顶点覆盖:
    • 定义:假如选了一个点就相当于覆盖了以它为端点的所有边。最小顶点覆盖就是选择最少的点来覆盖所有的边。
    • 定理:最小顶点覆盖等于二分图的最大匹配。
  • 最大独立集:
    • 定义:选出一些顶点使得这些顶点两两不相邻,则这些点构成的集合称为独立集。找出一个包含顶点数最多的独立集称为最大独立集。
    • 定理:最大独立集 = 所有顶点数 - 最小顶点覆盖 = 所有顶点数 - 最大匹配

二分图的判定

一般用染色法判断一个图是否为二分图。染色法的基本思想是,如果一个图是二分图,那么任何一个顶点的邻居都应该属于与该顶点不同的集合,因此可以用两种颜色对图中的顶点进行染色,以检验是否满足这个条件。

模板:P1330 封锁阳光大学 - 洛谷
裸的二分图判定,注意图不是连通的,所以每一个连通块选择的颜色可能不一样,我们应该选两种颜色中出现次数较小者。

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

// 图不是连通的,所以对于每一个连通块,所选点的颜色可能不一样。
// 因此需要用一个桶来记录同一个连通块中两种颜色出现的次数,并选择其中的较小者。
const int maxn = 1e4 + 5;
const int maxm = 1e5 + 5;
int n, m;
int head[maxn], color[maxn], counter[maxn];
struct Edge {
	int to, next;
} edge[maxm << 1];

inline void insertEdge(int u, int v) {
	static int edgecnt;
	edge[++edgecnt].to = v;
	edge[edgecnt].next = head[u];
	head[u] = edgecnt;
}

bool dfs(int u, int col) {
	color[u] = col;
	counter[color[u]]++;
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		if (!color[v]) {
			if (!dfs(v, 3 - col)) {
				return false;
			}
		} else if (color[v] == color[u]) {
			return false;
		}
	}
	return true;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		insertEdge(u, v);
		insertEdge(v, u);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		if (!color[i]) {
			counter[1] = counter[2] = 0; // 对每一个联通块都要先清空。
			if (!dfs(i, 1)) {
				printf("Impossible");
				return 0;
			}
			ans += min(counter[1], counter[2]);
		}
	}
	printf("%d", ans);
	return 0;
}

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

原理就是寻找增广路,在上文有提到。

算法步骤:

  1. 初始化:选择一个未匹配的左半部顶点作为起始点,尝试与其相连的右半部
    顶点进行匹配。
  2. 深度优先搜索:从起始点出发,沿着增广路径进行深度优先搜索,直到找到
    一个未匹配的右半部顶点或者路径不通为止。
  3. 匹配调整:如果找到了未匹配的右半部顶点,就将起始点和这个顶点之间的
    边加入到匹配中,并将路径上的所有非匹配边改为匹配边,将匹配边改为非
    匹配边。
  4. 重复步骤:如果路径不通,回退到前一个未匹配的左半部顶点,尝试与其相
    连的其他右半部顶点进行匹配,直到找到一个可行的匹配或者所有可能的匹
    配都尝试完毕。
  5. 终止条件:当所有左半部的顶点都尝试过匹配,且没有找到新的增广路径
    时,算法结束。

模板:P3386 【模板】二分图最大匹配 - 洛谷

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

const int maxn = 505;
int n, m, e;
int graph[maxn][maxn]; // graph[u][v] 表示左边的 u 与右边的 v 是否有连边。
int match[maxn]; // match[v] = u 表示右部节点 v 被左部的 u 匹配。
bool visited[maxn]; // visited[v] 表示右部节点 v 是否被左部匹配。

bool dfs(int u) {
	for (int v = 1; v <= m; v++) { // 枚举右部节点
		// 寻找增广路
		if (graph[u][v] && !visited[v]) {
			visited[v] = true; // 标记为已匹配,接下来不会再被匹配。
			// 如果 v 没有匹配对象,或 v 的匹配对象能找到新的匹配对象
			// 则 v 与 u 相匹配(找到增广路)
			if (!match[v] || dfs(match[v])) {
				match[v] = u;
				return true;
			}
		}
	}
	// 尝试了所有匹配,没有找到新的增广路。
	return false;
}

int main() {
	scanf("%d%d%d", &n, &m, &e);
	for (int i = 1; i <= e; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		graph[u][v] = 1;
	}
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += dfs(i);
		memset(visited, 0, sizeof(visited));
	}
	printf("%d", ans);
	return 0;
}
posted @   jxyanglinus  阅读(89)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示