【洛谷P3386】二分图最大匹配之Kuhn算法/匈牙利算法:直观理解

题目:洛谷P3386 【模板】二分图最大匹配

🥕 匈牙利算法本来是针对带权图最大匹配的,这里由于题目只是求最大匹配的边数,所以我们也只考虑无权的情况。

🚀 本文旨在服务于看了别的关于匈牙利算法的文章但不甚理解的童鞋,希望能够从直观上帮大家看清算法的巧妙之处,而关于匈牙利算法的历史渊源、主要流程等则不再赘述。


关于二分图最大匹配我们可以有如下直观的理解:我们假设有一群人和一堆物品,我们知道每个人喜欢这些物品中的哪几个,每个人可以分得一个物品,求最多可以使多少人分到自己喜欢的某个物品。我们令人的编号是ABCDEF……,物品的编号是123456……。

⭐️ 那好啊,我们先看人A。他喜欢哪个物品,我们就先给他呗,随便选一个他喜欢的就行(当然如果他什么都不喜欢就只能晾在一边了)。设分给A的物品为x。
⭐️ 再看人B。如果他喜欢一个没有分配给A的物品y,那太好了,把y分配给B即可。但如果B只喜欢已经分配给A的那个物品x,B就只能空手而归了吗?也不尽然。我们可以把x从A手里抢过来给B,然后看给A能不能分配一个别的。如果A除了喜欢x以外还喜欢一个物品z,那就好办了,把z给A,把x给B,皆大欢喜。但如果A也喜欢物品x,那没办法,A和B势必只能满足一个,没法在把x给B的情况下给A补偿一个别的,所以只能委屈一下B了。

……

⭐️ 不管考虑哪个人(设为P),我们总是可以遵循以下套路:如果P喜欢的物品中有未被认领的,那么直接分配给P就好了;否则就依次检查P喜欢的物品,如果可以想办法让这个物品的持有者Q另觅他物,那就把这个物品抢过来;如果他抢夺所有喜欢的物品都失败了(即物品持有者都没法腾出来),那他就分不到物品了。此外,在令当前物品持有者Q更换物品的时候,也需要进行相同的流程:如果Q喜欢的物品当中还有其他未被占领的,就分配给Q,否则也是看他喜欢的物品除了给P的之外有没有可能让持有者腾出来。那么,这就形成了一个递归结构,如果某一层的人获得了未被占领的物品,那么就P就被分配到了物品;如果所有辗转腾挪的方法都无效,那么P就不会获得物品。注意:递归过程中需要维护一个栈,用于标记哪些人缺物品,每次递归需要把发起人压入栈中;往更深递归时不能再回头向这些栈中的人索要物品(这个由一个vis数组实现)不然的话会形成回路,即A向B要物品,B向C要物品,C向D要物品,D又向栈中的B要物品,B又向C要……就无限循环了

⭐️ 所以,什么时候一个(我们所考虑的)新人能获得一个心仪的物品呢?那就是:要么直接有一个他喜爱的物品未被占有;要么他向一个人P要一个他喜爱的物品,这个人P向另一个人Q要,Q又向R要,……,直到Y向Z要,且Z恰好可以重新占有一个他喜爱的空闲物品的时候(前者可看作后者的一个特殊情况)。这个链条就是大名鼎鼎的增广路,可以表示为:(比如)A-1-B-2-C-3-…-H-8。增广路一定有奇数条边,其中A-1的含义是“人A想要抢夺物品1”,1-B的含义是“因为B目前占有物品1,所以他被逼着找别的物品”;最后H-8代表“终于,人H找到了另一个心爱的物品8,其中8目前是空闲状态”。如果有一个人A找到了由他开始的一条增广路,那么他就可以分配到物品。上面提到的递归过程就是一个寻找可行增广路的过程。

⭐️ 总结一下,目前的算法流程是:按任意顺序扫描每个人,对于每个人通过递归方法求出他能不能通过找到一条从他开始的增广路来分配到一个物品;在递归过程中不能回过头问栈中的人要物品,因为他们本来就是缺物品的状态,如果这样会产生无限循环。

🤔 但是,这样的算法实现出来在洛谷上会有三个点TLE。有一个值得注意的细节是:我们只是禁止向递归栈中的人索取物品,但是如果一条递归路径走投无路回溯的时候,一些人会被弹出栈外。这意味着递归过程可能访问同一个人多次(因为他被弹出栈后就可以变成被访问了;换言之,vis数组中他可能重新被标记为未访问状态)。

😕 你可能会问:难道我们可以只访问每个人一次吗?难道不同的栈格局访问同一个人,是否能形成合法增广路的结果是一样的吗? ——说实话,这个问题我想了很久。

💡 还真是。在我后面的代码中,每次扫描到新的人重置一遍vis数组,且递归到某个人直接令vis[他]=true,如果已经为true则直接返回增广路寻找失败。这是我觉得这个算法最玄妙的地方——如果在某个栈格局下寻找以人P开始的增广路失败,那我们在后面就直接放弃这个人了(即便在另一个栈格局下从P开始可能会成功)。那么我们自然要问:这样不会漏掉一些可行的增广路,导致算法在本该返回成功的时候返回失败吗?

🌈 想要证明不会漏掉情况,就要证明:如果有一个被vis[P]=true排除掉的成功增广路 β \beta β,那么一定存在另一个不含P的成功增广路 α \alpha α,使得我们的算法可以发现该增广路 α \alpha α并返回成功。(这样我们就能绕开“P被禁止访问了”这个限制。)在这之前,我们还因为在栈格局 γ \gamma γ(即一个残缺的、失败的增广路)下遇到P无功而返,而把P标记为不可访问(就算这时把vis[P]标记为true的)。这个失败的栈格局 γ \gamma γ也非常重要。
首先, β \beta β中遇到P后并未无功而返,而是成功找到了后续的增广路。那为什么在 γ \gamma γ下却失败了呢?只有一种解释: β \beta β中有P后面的人在 γ \gamma γ的前缀中出现过,但在 γ \gamma γ下因为不能回头问栈中的人要物品所以就不可访问了。
现在,我要开始施展魔法了:取 β \beta β中P后面最后一个出现在 γ \gamma γ前缀中的人T,并把 γ \gamma γ中T后面的部分替换为 β \beta β中T后面的部分,得到 α \alpha α。那么这个 α \alpha α一定是合法增广路:因为 β \beta β中T后面的部分都没有在 γ \gamma γ前缀中出现过的人了(毕竟T已经是最后一个出现的了),而 α \alpha α继承了 γ \gamma γ的一部分前缀和 β \beta β的一部分后缀,所以 α \alpha α中并没有同一个人被访问两次的情况。况且 α \alpha α是不含P的,所以我们的算法不会把 α \alpha α排除出去。
当然你可能会说:如果 α \alpha α也因为另一个人Q被标记为vis[Q]=true而被排除了呢?那就把 α \alpha α当作新的 β \beta β,“排除Q的失败增广路前缀”为新的 γ \gamma γ,重复前后缀拼接的操作,……直到迭代了若干次后的 α \alpha α没有被排除为止。至此,我们便证明了每个人在每一轮中只被访问一次的合理性。这也就保证了算法的复杂度为 O ( ∣ V ∣ ∣ E ∣ ) O(|V||E|) O(V∣∣E)(即人数与边数之积):每个人被扫描到;每次扫描一个人,可以保证所有的人都只被访问一次,而每次访问可能会检查他所有喜欢的物品(即他的所有边),故每次扫描至多访问所有边。


💻 代码实现:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <queue>using namespace std;const int MAXN = 1005;
vector<int> e[MAXN];
int n, m, t, match[MAXN], vis[MAXN];bool dfs(int u)
{if(vis[u]) return false;vis[u] = true;for(int it = 0; it < e[u].size(); ++it){int v = e[u][it];if(!match[v] || dfs(match[v])){match[v] = u;return true;}}return false;
}int solve()
{int ans = 0;for(int i = 1; i <= n; ++i){memset(vis, '\0', sizeof(vis));ans += dfs(i);}return ans;
}int main()
{cin >> n >> m >> t;int u, v;while(t--){cin >> u >> v;e[u].push_back(v);}cout << solve() << endl;return 0;
}

参考:https://cp-algorithms.com/graph/kuhn_maximum_bipartite_matching.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/81594.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】二分查找(返回插入点)5.14

二分查找基础版 package 二分查找; public class BinarySearch { public static void main(String[] args) { // TODO Auto-generated method stub } public static int binarySearchBasic(int[] a,int target) { int i0,ja.length-1; //设置指针初值 while…

Ubuntu 命令

Ubuntu 命令速查表​ ​分类​​命令​​功能描述​​示例/常用选项​​​​文件与目录​ls列出目录内容ls -a&#xff08;显示隐藏文件&#xff09;; ls -lh&#xff08;详细列表易读大小&#xff09; cd切换目录cd ~&#xff08;主目录&#xff09;; cd ..&#xff08;上级…

Java集合框架详解与使用场景示例

Java集合框架是Java标准库中一组用于存储和操作数据的接口和类。它提供了多种数据结构&#xff0c;每种数据结构都有其特定的用途和性能特点。在本文中&#xff0c;我们将详细介绍Java集合框架的主要组成部分&#xff1a;List、Set和Queue&#xff0c;并通过代码示例展示它们的…

《Python星球日记》 第78天:CV 基础与图像处理

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、计算机视觉(CV)简介1. 什么是计算机视觉?2. 计算机视觉的应用场景3. 图像的基本属性a》像素(Pixel)b》通道(Channel)c》分辨率(Res…

LabVIEW在电子电工教学中的应用

在电子电工教学领域&#xff0c;传统教学模式面临诸多挑战&#xff0c;如实验设备数量有限、实验过程存在安全隐患、教学内容更新滞后等。LabVIEW 作为一款功能强大的图形化编程软件&#xff0c;为解决这些问题提供了创新思路&#xff0c;在电子电工教学的多个关键环节发挥着重…

【优选算法 | 字符串】字符串模拟题精选:思维+实现解析

算法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;双指针滑动窗口二分查找前缀和位运算模拟链表哈希表 在众多字符串算法题中&#xff0c;有一类题目看起来没有太多算法技巧&#xff0c;却经常让人“翻车”——那就是字符串模拟题。这类题型往往不依赖复杂的数据…

虚幻引擎5-Unreal Engine笔记之Default Pawn与GamMode、Camera的关系

虚幻引擎5-Unreal Engine笔记之Default Pawn与GamMode、Camera的关系 code review! 文章目录 虚幻引擎5-Unreal Engine笔记之Default Pawn与GamMode、Camera的关系1.Default Pawn与Camera的关系1.1. Default Pawn 是什么&#xff1f;1.2. Default Pawn 的主要组件1.3. Default…

HarmonyOs开发之———UIAbility进阶

谢谢关注!! 前言:上一篇文章主要介绍开发之———使用HTTP访问网络资源:HarmonyOs开发之———使用HTTP访问网络资源-CSDN博客 代码资源:https://download.csdn.net/download/this_is_bug/90841580 一、基本概念 UIAbility 是 HarmonyOS 应用的核心组件,负责用户界面的…

java实现根据Velocity批量生成pdf并合成zip压缩包

Velocity 模版操作 用的之前写好的: 传送门 其中需要新加一个转成输入流的方法 public static InputStream convertToPdf(StringWriter stringWriter) throws IOException {//将 HTML 转为字节流byte[] htmlBytes stringWriter.toString().getBytes(StandardCharsets.UTF_8)…

SCDN能够运用在物联网加速当中吗?

在当今的科技化时代当中&#xff0c;物联网已经广泛渗透在各个领域行业当中&#xff0c;随着物联网规模的不断扩大&#xff0c;数据信息的传输速度和网络稳定性成为企业需要重视的两点因素&#xff0c;而SCDN也成为安全内容分发网络作为一种融合了内容加速和安全防护的技术&…

二程运输的干散货船路径优化

在二程运输中&#xff0c;干散货船需要将货物从一个港口运输到多个不同的目的地港口。路径优化的目标是在满足货物运输需求、船舶航行限制等条件下&#xff0c;确定船舶的最佳航行路线&#xff0c;以最小化运输成本、运输时间或其他相关的优化目标。 影响因素 港口布局与距离…

Oracle物理恢复相关注意点

如果需要恢复的数据库或者数据文件不存在&#xff0c;则需要将全量备份集RESTORE[ 将全量备份集恢复到目标数据库中&#xff0c;称之为RESTORE。]到目标数据库中&#xff0c;然后再RECOVER[ 将增量备份集或者归档日志恢复到目标数据库中&#xff0c;称之为RECOVER。]增量备份集…

C++ string小记

#include<string> using std::string;string s1; string s2 "hello" //初始化一个hello字符串 string s3(5,a) //连续5个字符a组成的串&#xff0c;即aaaaa///字符串操作int length s1.size() //.size()求字符串长度char c1 s1[1]; //从下标0开始&#xf…

自然语言处理入门级项目——文本分类(预处理)

文章目录 前言1.数据预处理1.1数据集介绍1.2数据集抽取1.3划分数据集1.4数据清洗1.5数据保存 2.样本的向量化表征2.1词汇表2.2向量化2.3自定义数据集2.4备注 结语 前言 本篇博客主要介绍自然语言处理领域中一个项目案例——文本分类&#xff0c;具体而言就是判断评价属于积极还…

C++面试2——C与C++的关系

C与C++的关系及核心区别的解析 一、哲学与编程范式:代码组织的革命 过程式 vs 多范式混合 C语言是过程式编程的典范,以算法流程为中心,强调“怎么做”(How)。例如,实现链表操作需手动管理节点指针和内存。 C++则是多范式语言,支持面向对象(OOP)、泛型编程(模板)、函…

HTTP与HTTPS协议的核心区别

HTTP与HTTPS协议的核心区别 数据传输安全性 HTTP采用明文传输&#xff0c;数据易被窃听或篡改&#xff08;如登录密码、支付信息&#xff09;&#xff0c;而HTTPS通过SSL/TLS协议对传输内容加密&#xff0c;确保数据完整性并防止中间人攻击。例如&#xff0c;HTTPS会生成对称加…

PotPlayer 安装 madVR、LAV Filters 以提升解码能力和视频音频效果

PotPlayer自带的解码器并不是最好&#xff0c;如下两张截图都是出自 TOP GUN: Maverick 较暗、灰蒙蒙的一张&#xff0c;是安装插件之前明亮的一张&#xff0c;是安装插件之后 详细安装参考 https://www.bilibili.com/video/BV1UV5qzuE74?spm_id_from333.788.videopod.sectio…

深入理解 OpenCV 的 DNN 模块:从基础到实践

在计算机视觉领域蓬勃发展的当下&#xff0c;深度学习模型的广泛应用推动着技术的不断革新。OpenCV 作为一款强大且开源的计算机视觉库&#xff0c;其 DNN&#xff08;Deep Neural Network&#xff09;模块为深度学习模型的落地应用提供了高效便捷的解决方案。本文将以理论为核…

Spring MVC 中请求处理流程及核心组件解析

在 Spring MVC 中&#xff0c;请求从客户端发送到服务器后&#xff0c;需要经过一系列组件的处理才能最终到达具体的 Controller 方法。这个过程涉及多个核心组件和复杂的映射机制&#xff0c;下面详细解析其工作流程&#xff1a; 1. 核心组件与请求流程 Spring MVC 的请求处…

RISC-V 开发板 MUSE Pi Pro V2D图像加速器测试,踩坑介绍

视频讲解&#xff1a; RISC-V 开发板 MUSE Pi Pro V2D图像加速器测试&#xff0c;踩坑介绍 今天测试下V2D&#xff0c;这是K1特有的硬件级别的2D图像加速器&#xff0c;参考如下文档&#xff0c;但文档中描述的部分有不少问题&#xff0c;后面会讲下 https://bianbu-linux.spa…