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

题目

给定一个二分图,其中左半部包含 \(n_1\) 个点(编号 \(1 \sim n_1\)),右半部包含 \(n_2\) 个点(编号 \(1 \sim n_2\)),二分图共包含 \(m\) 条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

二分图的匹配:给定一个二分图 \(G\),在 \(G\) 的一个子图 \(M\) 中,\(M\) 的边集 \(\{E\}\) 中的任意两条边都不依附于同一个顶点,则称 \(M\) 是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

输入格式

第一行包含三个整数 \(n_1\)\(n_2\)\(m\)

接下来 \(m\) 行,每行包含两个整数 \(u\)\(v\),表示左半部点集中的点 \(u\) 和右半部点集中的点 \(v\) 之间存在一条边。

输出格式

输出一个整数,表示二分图的最大匹配数。

数据范围

\(1 \le n_1,n_2 \le 500\),
\(1 \le u \le n_1\),
\(1 \le v \le n_2\),
\(1 \le m \le 10^5\)

输入样例:

2 2 4
1 1
1 2
2 1
2 2

输出样例:

2

题解

思路

找寻二分图内左半部分点集与右半部分点集之间的最大匹配数
  • 将左半部分\(n_1\)个点看成男生 右半部分\(n_2\)个点看成女生

  • 他们其中连的边看成当前男生与女生之间存在好感度可以结为情侣

  • 我们要找的就是最大情侣数

    怎么满足情侣数最大呢
  • 遍历所有男生去搜他们有好感的女生

  • 如果当前女生没有匹配其他男生,结为情侣

  • 如果匹配了另一个男生但发现他可以去匹配其他女生,那就让他去匹配其他女生,我们匹配当前女生 (ntr)

代码实现

关键点:为什么每次循环内都要清空st[]数组?
for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            match[j] = x;
            st[j] = true;
            return true;
        }
        else
        {
            if (find(match[j]))
            {
                match[j] = x;
                return true;
            }
        }
    }
  • 刚开始我是这么写的,这个写法的错误是如果当前女孩已经配对过了,会导致find(match[j])无限递归,因为每一次进入find(match[j])st[j]都为true,每次都会进入else判断,进入find(match[j]),从而无限递归

  • 如果每次不更新st[]false,发现match[j]被赋值过了,然后再去find(match[j]),进入find(match[j])内发现st[j] = truematch[j]被赋值,虽然match[j]就是他自己,但是他还是又去find(match[j]),所以就会一直递归下去。

    所以每一次搜索男生之前都要清空st[]

    发现没有 st数组才是匈牙利算法的精髓

正确算法就可以规避find(match[j])无限递归的问题,当st[j] = true的时候就去搜下一个点了,而不是进入错误代码里的else继续去递归find(match[j])

Tips:如果数组越界了,什么错误都可能发生(TLE RE Segmatation False 等等)

#include <bits/stdc++.h>

using namespace std;

const int N = 510, M = 1e5 + 10; //点的上限与边的上限

int h[N], e[M], ne[M], idx;
int n1, n2, m;
int res;
int st[N]; //用于判断当前右半部分点集是否搜索过(当前女生是否有了男朋友)
int match[N]; //存当前女生匹配的是哪个男生

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++ ;
}

bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i]; //这步经常容易忘写 注意!i实际上是指针
        if (!st[j]) //如果当前女生没有被搜过
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j])) //如果当前女生没有被匹配 或者 当前女生之前匹配的对象能匹配其他女生 当前女生就能匹配当前男生
            {
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

void solve()
{
    scanf("%d%d%d", &n1, &n2, &m);
    memset(h, -1, sizeof h);
    
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b); //这里只用构造从男生指向女生的边 因为我们只用枚举男生搜索他有好感的女生就能找出最大匹配数
    }
    
    for (int i = 1; i <= n1; i ++ )
    {
        memset(st, 0, sizeof st); //每次搜下一个男生前清空st数组
        if (find(i))
            res ++ ;
    }
    
    printf("%d\n", res);
    
    return;
}

int main()
{
    solve();
    
    return 0;
}
posted @ 2024-04-20 13:46  MsEEi  阅读(15)  评论(0)    收藏  举报