专栏文章

题解:P3386 【模板】二分图最大匹配

P3386题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mipc7tq2
此快照首次捕获于
2025/12/03 09:37
3 个月前
此快照最后确认于
2025/12/03 09:37
3 个月前
查看原文

P3386 【模板】二分图最大匹配 题解

1. 引入:

First . 二分图:

既然要学习二分图最大匹配,肯定先要了解啥是二分图。
OI wiki 上说:
二分图是什么?节点由两个集合组成,且两个集合内部没有边的图。
换言之,存在一种方案,将节点划分成满足以上性质的两个集合。
就是把一个图上的点分成两组,每一条边都连接不同组的点。
在图中表示为每一条边都从 UU 组的点连到 VV 组的点,不会从 UU 组的点连到 UU 组的点。

second . 二分图匹配:

首先了解二分图匹配(来自 OIwiki):
在图论中,假设图 G=(V,E)G=(V,E)
其中 VV 是点集,EE 是边集。
一组两两没有公共点的边集 M(ME)M(M \in E) 称为这张图的匹配。
定义匹配的大小为其中边的数量 M|M|,其中边数最大的 MM 为 最大匹配。
啥意思?
当年的我看见这条定义觉得同样很懵。
那我们不管普通的图,只考虑二分图。
其实就是:
好多条无公共端点边的集合叫这张二分图的一个匹配。(这里因为是二分图,所以边已经是连接两组点集 UUVV 的了,不需要加以限制)
匹配的大小为边的数量。
应该清楚多了吧。
了解二分图匹配之后,就可以学习二分图最大匹配了。

third . 二分图最大匹配:

顾名思义,就是二分图的所有匹配中边数最多的那个匹配。
OI wiki 同样给出定义:
假设图有 nn 个顶点,mm 条边。
给定一个二分图 G,要求选出一些边,使得这些边没有公共顶点,且边的数量最大
本题的目的就是实现这个要求。
下面开始讲算法思路。

2. 思路:

准备

本题的解法为匈牙利算法。
首先我们有一张二分图:
二分图
随便连上几条边:
二分图2

算法过程:

先将 00 号点连接到可以连接的第一个点即 44 号。
二分图3
然后将 11 号点连接到可以连接的第一个点即 55 号。
二分图4
接着连接 22 号点,但此时我们发现 22 号点应该连接的 44 号被 00 号占用了。 这时,我们将 00 号点与 44 号点间的连接断开。
二分图5
这时发现 00 号点和 11 号点同时连接到了 55 号点,与二分图最大匹配的定义不符,所以再为 11 号点重新连接到 66 号点。
二分图6
于是就可以把 22 号点与 44 号点连接起来了。
二分图7
再往后的 33 号点应连接到的 66 号点被占用,无法连接,算法结束。 最大匹配值即为已连接上的边的数量。
注意:匈牙利算法求解的结果中,匹配的边的数量一定为最大,但匹配方式不唯一。

3. 证明:

我们定义:
交错路:始于非匹配点由匹配边于非匹配边交错而成的路径。
增广路:始于非匹配点且终于非匹配点(除了起始的点)的交错路。增广路中边的数量是奇数。
根据增广路定义可知:
推论 1. 增广路中匹配边数量比非匹配边数量少一。
通过这两条定义,联系算法过程,不难发现:
  1. 在匈牙利算法中,我们通过寻找每个未匹配点的增广路来增加匹配数量。
  2. 匈牙利算法结束时意味着没有增广路了。
所以,要证明:
匈牙利算法得到的匹配为这个二分图的最大匹配。
就被转换成了:
二分图的最大匹配中不存在增广路。
直接证不好证,采用反证法。
即证明:
二分图的最大匹配中有增广路。
不成立。
证明:
假设二分图最大匹配中有增广路:
则根据推论 1,我们将图中匹配边与非匹配边反转后,得到一个新的匹配。
原非匹配边反转成了匹配边,此时相当于匹配边 +1+1
所以,此时原图中存在更大的匹配。
故原命题不成立。
证毕。
所以:匈牙利算法得到的匹配为这个二分图的最大匹配。
接下来是代码,结合上面文章内容理解应该很简单。

4.Code:

vector 存边,深搜跑算法,重新连接的条件很重要。
CPP
#include<iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 1005;

int n, m, t;
int mch[maxn], vistime[maxn];

vector<int> e[maxn];

bool dfs(const int u, const int tag);

int main() {
  scanf("%d %d %d", &n, &m, &t);
  for (int u, v; t; --t) {
    scanf("%d %d", &u, &v);
    e[u].push_back(v);
  }
  int ans = 0;
  for (int i = 1; i <= n; ++i) if (dfs(i, i)) {
    ++ans;
  }
  printf("%d\n", ans);
}

bool dfs(const int u, const int tag) {
  if (vistime[u] == tag) return false;
  vistime[u] = tag;
  for (auto v:e[u]) if ((mch[v] == 0) || dfs(mch[v], tag)) {
    mch[v] = u;
    return true;
  }
  return false;
}
全文完。

5. 参考资料:

  1. OIWiKi。
  2. Loi23 级学长课件。

评论

0 条评论,欢迎与作者交流。

正在加载评论...