54.Acwing基础课第861题-简单-二分图的最大匹配

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;
}
posted @ 2026-04-09 01:45  CodeMagicianT  阅读(1)  评论(0)    收藏  举报