如何用二分图算法求最大匹配?

摘要:54.Acwing基础课第861题-简单-二分图的最大匹配 题目描述 (给定一个二分图,其中左半部包含 n_1 个点(编号 1∼n_1),右半部包含 n_2 个点(编号 1∼n_2),二分图共包含 m 条边。) (数据保证任意一条边的
54.Acwing基础课第861题-简单-二分图的最大匹配 题目描述 \(给定一个二分图,其中左半部包含 n_1 个点(编号 1∼n_1),右半部包含 n_2 个点(编号 1∼n_2),二分图共包含 m 条边。\) \(数据保证任意一条边的两个端点都不可能在同一部分中。\) \(请你求出二分图的最大匹配数。\) \(二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配\)。 \(二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数\)。 输入格式 \(第一行包含三个整数 n1、 n2 和 m\)。 \(接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边\)。 输出格式 输出一个整数,表示二分图的最大匹配数。 数据范围 \(1≤n_1,n_2≤500\), \(1≤u≤n_1\), \(1≤v≤n_2\), \(1≤m≤10^5\) 输入样例: 2 2 4 1 1 1 2 2 1 2 2 输出样例: 2 代码: // 包含memset函数所需头文件(用于初始化邻接表和访问标记数组) #include <cstring> // 标准输入输出头文件(scanf/printf依赖) #include <iostream> // 算法库(本代码未直接使用,属于通用模板保留) #include <algorithm> // 使用std命名空间,避免重复写std:: using namespace std; // 常量定义: // N:二分图左右集合的最大顶点数(题目通常约束510级别) // M:最大边数(适配稀疏图场景) const int N = 510, M = 100010; // 全局变量: int n1, n2, m; // n1:左集合顶点数,n2:右集合顶点数,m:边数 // 邻接表存储二分图(仅存左集合→右集合的边,无需双向): int h[N]; // h[u]:左集合顶点u的邻接表表头(初始为-1) int e[M]; // e[idx]:第idx条边的终点(右集合顶点) int ne[M]; // ne[idx]:第idx条边的下一条边索引(链表结构) int idx; // 边计数器(新增边时自增) int match[N]; // 匹配数组:match[j]表示右集合顶点j当前匹配的左集合顶点(0表示未匹配) bool st[N]; // 访问标记数组:标记右集合顶点j是否被「当前左顶点」访问过(避免重复递归) // 邻接表添加边函数(仅添加左→右的边,二分图匹配方向固定) // a:左集合顶点,b:右集合顶点 void add(int a, int b) { e[idx] = b; // 记录第idx条边的终点是右顶点b ne[idx] = h[a]; // 让第idx条边指向左顶点a当前的表头(链表前驱) h[a] = idx ++ ; // 更新a的表头为当前边索引,边计数器+1 } // 核心递归函数:查找左集合顶点x的匹配(找增广路) // 返回值:true=找到匹配,false=未找到匹配 // 增广路思想:从x出发,找到一条“未匹配边→匹配边→未匹配边”的路径,扩展匹配数 bool find(int x) { // 遍历左顶点x的所有邻接右顶点(邻接表遍历) for (int i = h[x]; i != -1; i = ne[i]) { int j = e[i]; // j是x的邻接右顶点 if (!st[j]) // 若右顶点j未被当前左顶点x访问过(避免重复递归导致死循环) { st[j] = true; // 标记j已被访问,后续同一轮递归不再处理 // 匹配成功的两种情况: // 1. 右顶点j未匹配(match[j]==0),直接将x与j匹配; // 2. j已匹配,但j的匹配左顶点match[j]能找到其他匹配(递归找增广路),则j“让”给x if (match[j] == 0 || find(match[j])) { match[j] = x; // 将右顶点j匹配给左顶点x return true; // 匹配成功,返回true } } } return false; // x的所有邻接右顶点都无法匹配,返回false } int main() { // 输入:左集合顶点数n1,右集合顶点数n2,边数m scanf("%d%d%d", &n1, &n2, &m); // 初始化邻接表表头:所有左顶点的表头设为-1(初始无邻边) memset(h, -1, sizeof h); // 输入m条边,构建左→右的邻接表 while (m -- ) { int a, b; scanf("%d%d", &a, &b); // a∈左集合,b∈右集合 add(a, b); // 仅添加左→右的边,无需反向 } int res = 0; // res:二分图最大匹配数 // 遍历左集合的每一个顶点,尝试为其找匹配 for (int i = 1; i <= n1; i ++ ) { // 关键:每次处理新的左顶点i时,清空访问标记数组st // 原因:st仅约束“同一左顶点”的递归过程,不同左顶点的访问标记互不影响 memset(st, false, sizeof st); if (find(i)) res ++ ; // 若i找到匹配,匹配数+1 } // 输出二分图最大匹配数 printf("%d\n", res); return 0; }