什么是二分图

如果一个无向图的的顶点可以分为两个互不相交的子集A和B,那么它就是二分图。也就是说,A、B内部不存在连边,所有连边都一头连着A中的顶点,另一头连着B中的顶点。

什么是二分图最大匹配?

二分图最大匹配问题,就是在A、B这两个集合中,不断选择两个存在连线的点,把他们连起来,求最多可以有多少条连线的问题。

怎么解?

匈牙利算法的核心在于:从A集合中选择一个点,然后将与其相连的B中的点依次对照,如果B中的点尚未匹配,那就将这两个点进行匹配,然后遍历A中的下一个点。接着继续访问与其相连的B中的点,如果B中的点已经被匹配了,那就尝试递归地与B中的这个点相匹配的A中的点换一个匹配对象(递归地执行上述过程)。这其实就是在寻找增广路。当找到一条增广路,就能使得匹配数+1。如此一来,当我们把A中的所有点遍历之后,就能得到最大的匹配了。

上面这个过程说起来有点绕口,我也想了很久才想明白。

在敲代码的时候还要注意一点,我们一直都是对于A中的点进行DFS,而被访问到的B中的点,需要在被访问到的时候,设置vis为true, 否则会造成死循环。

上面说的“对A中的点进行DFS”怎么理解呢?由于增广路是:未匹配边-匹配边-未匹配边-匹配边-未匹配边,即开头结尾都要是未匹配边,因此我们设定A集合出发的边都是未匹配边,B集合出发的都是匹配边。然后,由于匹配边是确定的,因此在寻找增广路时,不需要对B中的点进行DFS,只需将与B点匹配的A中的点进行DFS即可,这样才能满足增广路的要求。

题目

这是一道模板题:https://uoj.ac/problem/78

从前一个和谐的班级,有 nl 个是男生,有 nr 个是女生。编号分别为 1,…,nl 和 1,…,nr。

有若干个这样的条件:第 v 个男生和第 u 个女生愿意结为配偶。

请问这个班级里最多产生多少对配偶?

输入格式
第一行三个正整数,nl,nr,m。

接下来 m 行,每行两个整数 v,u 表示第 v 个男生和第 u 个女生愿意结为配偶。保证 1≤v≤nl,1≤u≤nr,保证同一个条件不会出现两次。

输出格式
第一行一个整数,表示最多产生多少对配偶。

接下来一行 nl 个整数,描述一组最优方案。第 v 个整数表示 v 号男生的配偶的编号。如果 v 号男生没配偶请输出 0。

样例一
input
2 2 3
1 1
1 2
2 1

output
2
2 1

explanation
1 号男生跟 2 号女生幸福地生活在了一起~

2 号男生跟 1 号女生幸福地生活在了一起~

样例二
input
2 2 2
1 1
2 1

output
1
1 0

explanation
班上一个女神一个女汉子,两个男生都去追女神。一种最优方案是:

1 号男生跟 1 号女生幸福地生活在了一起~

2 号男生孤独终生。= =||

限制与约定
1≤nl,nr≤500,1≤m≤250000。

时间限制:1s
空间限制:256MB

这很明显是一个二分图最大匹配问题,由于男生女生的编号都是从1开始,因此为了能便于区分,我们将女生的编号x暂时设置为x+nl, 这样就能保证每个人编号的唯一性。

代码如下:

//二分图最大匹配
#include <bits/stdc++.h>
using namespace std;

#define MAXN 505
#define INF (1 << 31)

bool vis[MAXN * MAXN];
int nl, nr, m;
int girl[MAXN];
int match[MAXN * 3];

struct edge
{
    int from, to, next = 0;
} e[MAXN * MAXN];

int head[3 * MAXN];
int js_edges = 0;

void add(int u, int v)
{
    e[++js_edges].from = u;
    e[js_edges].to = v;
    e[js_edges].next = head[u];
    head[u] = js_edges;
}

bool dfs(int x)
{
    vis[x] = true;
    //cout << "x " << x << endl;
    int nd = head[x];
    while (nd != 0)
    {
        if (!vis[e[nd].to])
        {
            vis[e[nd].to]=true;//防止来回横跳,无法退出
            //cout << "     " << e[nd].to << endl;
            if (!match[e[nd].to] || dfs(match[e[nd].to]))
            {
                match[x] = e[nd].to;
                match[e[nd].to] = x;
                
                return true; //找到增广路或者能够创建连线
            }
        }
        nd = e[nd].next;
    }

    return false;
}

int main()
{
    scanf("%d%d%d", &nl, &nr, &m);

    int v, u;
    for (int i = 0; i < m; ++i)
    {
        scanf("%d%d", &v, &u);
        //人的id划分:男生都加nr, 女生不变
        add(v, u + nl);
    }

    int ans = 0;
    for (int i = 1; i <= nl; ++i)
    {
        memset(vis, 0, sizeof(vis));
        if (dfs(i))
            ++ans;
    }

    printf("%d\n", ans);
    for (int i = 1; i <= nl; ++i)
    {
        if (match[i] == 0)
            printf("0 ");
        else
            printf("%d ", match[i] - nl);
    }
    printf("\n");
}

转载请注明原文:https://www.longjin666.top/?p=1122

欢迎关注我的公众号“灯珑”,让我们一起了解更多的事物~

你也可能喜欢

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注