【喜闻乐见,包教包会】二分图最大匹配:匈牙利算法(洛谷P3386)

news2024/12/25 0:15:46

🎭不要管上面那玩意。。。

引入

现在,你,是一位酒店的经理。

西装笔挺,清瘦智慧。

金丝眼镜,黑色钢笔。

大理石的地板,黑晶石的办公桌,晶莹的落地玻璃。

而现在,有几个雍容华贵的贵妇要入住你的酒店,她们各有一些要求。

请你最大限度地满足她们的要求。

这,就是二分图最大匹配的一个现实问题(《非诚勿扰》也是一种)

前置知识

链式前项星

链式前向星是一种图的存储结构,它以链表形式存储每个顶点的出边信息,其中每个节点的结构如下:

struct Edge {
    int to, next;
};

其中,to表示该边指向的顶点编号,next表示与该顶点相邻的下一个顶点在链表中的位置。因此,链式前向星可以使用一个数组来存储每个顶点的邻接链表,如下所示:

const int N = 1e5 + 10;

// head[i]表示顶点i的邻接链表的头结点在edges数组中的下标
int head[N];

// edges数组用来存储所有顶点的出边信息,其中的每个元素表示一个边
Edge edges[N];

因为在链式前向星中,一个顶点的出边被存储在链表中,因此需要指定每个顶点邻接链表的头结点在edges数组中的位置。当需要访问该顶点的出边信息时,只需要从该链表的头结点开始遍历即可。

下面,我们来看一下链式前向星的代码实现。假设我们需要构建一个无向图,其中有n个顶点和m条边,那么可以使用以下代码来构建邻接链表:

void addEdge(int u, int v) {
    edges[cnt].to = v;
    edges[cnt].next = head[u];
    head[u] = cnt++;

    edges[cnt].to = u;
    edges[cnt].next = head[v];
    head[v] = cnt++;
}

void init() {
    memset(head, -1, sizeof(head));
    cnt = 0;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        addEdge(u, v);
    }
}

在上述代码中,addEdge函数用于向无向图中添加一条边。因为是无向图,所以需要同时添加两条边,一条从uv,一条从vu。在添加边的过程中,需要在edges数组中存储两个结构体分别表示这两条边的信息,并将它们分别添加到uv的邻接链表的头部。

初始化函数init则用于初始化head数组和cnt变量。在函数中,head数组被初始化为-1,表示所有顶点的邻接链表都为空。cnt变量则表示当前已经添加的边的数量,一开始初始化为0

使用链式前向星可以实现高效的图算法,比如深度优先遍历和广度优先遍历。因为链式前向星只存储与每个顶点相邻的边,所以可以避免遍历不必要的边,提高算法的运行效率。

二分图

二分图是指一个无向图的所有顶点可以被分为两个互不相交的子集,使得同一子集内的顶点之间没有边相连。也可以说,如果将图中的顶点分为两个集合,那么图中所有的边都是将一个集合中的顶点与另一个集合中的顶点相连。这种图形状就像是将顶点分为两个颜色,每个颜色内的顶点之间没有边相连,而不同颜色的顶点之间都有边相连。

举个例子,可以将需要交际的人分为两个集合A和B,A集合中的人互相认识,B集合中的人也互相认识,但是A集合中的人与B集合中的人互相不认识。用图来表示的话,就是将A集合中的每个人与B集合中的每个人之间连一条边。

二分图有许多应用,比如匹配问题、调度问题、最大独立集、任务分配等。其中最常见的应用是匹配问题,即给定一个图,找到图中的最大匹配,也就是寻找一种方案,使得图中的最多的边满足两端都是不同颜色的顶点。

最大匹配

 最大匹配:选出的子集包含边的个数最大。

 举个例子:

在下图中,无论如何画都无法匹配出超过三对,3就是最大匹配。

 (红边表示被匹配的边)

模板题目

【模板】二分图最大匹配

题目描述

给定一个二分图,其左部点的个数为 n,右部点的个数为 m,边数为 e,求其最大匹配的边数。

左部点从 1 至 n 编号,右部点从 1 至 m 编号。

输入格式

输入的第一行是三个整数,分别代表 $n$,$m$ 和 $e$。

接下来 e 行,每行两个整数u, v,表示存在一条连接左部点 u 和右部点 v 的边。

输出格式

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

样例

#1

输入
1 1 1
1 1

输出 
1
 

#2

输入
4 2 7
3 1
1 2
3 2
1 1
4 2
4 1
1 1

输出
2

提示

数据规模与约定

对于全部的测试点,保证:

  • 1≤n,m≤500
  • 1≤e≤5×10^4
  • 1≤u≤n,1≤v≤m。

不保证给出的图没有重边。

 题目很简洁,就不进行分析了。向下看,看这题怎么解。

算法原理

下面采用一种 喜 闻 乐 见 的方式进行演示。

突然发现上面的引入太无聊了,那么就换个例子吧。

既然是匹配,那就来找女朋友……

😍😍😍😍😍😍😍😍😍😍😍

图文趣味演示

图片用的是我中学同学的。

点集是这样的

(为何B 集还有♂的?经费有限。。。)

至于边集 ,随便连几条线吧。

 用数据表示出来就是这样。(输入)

5 5 8

1 6

1 8

1 10

2 7

3 9

3 10

4 9

5 6 

(关于输入格式,自行看上面的模板题目部分)

然后,我们设:

vis[i]存B集i号点是否被访问。

match[i]存与i配对的点的编号。

好,开始游戏。

首先看1号点。

第一个找到的就是6号点,那么就将match[1]=6,match[6]=1,配对成功。

然后是二号点,很显然,他只能找7号,那就配对吧。match[2]=7,match[7]=2;

三号,是9号。

至此,图是这样。(红色边连接配对两点)

 之后,到了4号点。

他也选择9号点,但已经被3号选择了,他就问3号,你可以把9好让出来吗?

3号备胎多心胸广大,就搜索自己其他的边。找到了,那就告诉4号,可以。因此,3号便换成了10号,4号换成了9号。

………………

最后,成了这样。

 因此,该图的最大匹配为5。

据此易得,有如下几个步骤。

1.依次遍历个点

存图之后,就用for循环对A集合的点进行遍历。

每到一个点,就清空vis数组,进行dfs操作。

2.递归寻找匹配者

若此人没有匹配,或者其匹配者可以让出,就匹配成功,返回true。

否则,若遍历其所有出边都找不到,返回false。

代码如下:

bool dfs(int x)
{
	for(int k=last[x];k;k=e[k].pre)
	{
		int y=e[k].y;
		if(vis[y]) continue;
		vis[y]=true;
		if(!match[y]||dfs(match[y]))
		{
			match[y]=x;
			return true;
		}
	}
	return false;
}

代码

#include<bits/stdc++.h>
using namespace std;
const int N=10010;
struct edge{
    int x,y,pre;
}e[200010];
int last[N],elen=0,ans;
int n,m;
int vis[N],match[N];
void init(int x,int y)
{
	e[++elen]=edge{x,y,last[x]};
	last[x]=elen;
}
bool dfs(int x)
{
	for(int k=last[x];k;k=e[k].pre)
	{
		int y=e[k].y;
		if(vis[y]) continue;
		vis[y]=true;
		if(!match[y]||dfs(match[y]))
		{
			match[y]=x;
			return true;
		}
	}
	return false;
}
int main()
{
	int ll;
	scanf("%d%d%d",&n,&m,&ll);
	for(int i=1;i<=ll;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		init(x,y);	
	}
	for(int i=1;i<=n;i++)
	{
		memset(vis,0,sizeof(vis));
		if(dfs(i))
		{
			ans++;
		}
	}
	printf("%d",ans);
}

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

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

相关文章

Spring高手之路——深入理解与实现IOC依赖查找与依赖注入

本文从xml开始讲解&#xff0c;注解后面给出 文章目录 1. 一个最基本的 IOC 依赖查找实例2. IOC 的两种实现方式2.1 依赖查找&#xff08;Dependency Lookup&#xff09;2.2 依赖注入&#xff08;Dependency Injection&#xff09; 3. 在三层架构中的 service 层与 dao 层体会依…

Opencv(图像处理)-基于Python-绘图功能

1.介绍2. line()3.rectangle()4.circle()5. ellipse()6.polylines()7.fillPoly()8. putText()代码示例9.用鼠标在图片上作图 1.介绍 OpenCV为开发者还提供了绘图功能&#xff0c;我们可以通过函数来实现在图片上作图。 2. line() 画线 cv2.line(img&#xff0c;开始点&#x…

G0第23章:GORM基本示例、GORM Model定义、主键、表名、列名的约定

04 GORM基本示例 注意: 本文以MySQL数据库为例&#xff0c;讲解GORM各项功能的主要使用方法。 往下阅读本文前&#xff0c;你需要有一个能够成功连接上的MySQL数据库实例。 Docker快速创建MySQL实例 很多同学如果不会安装MySQL或者懒得安装MySQL&#xff0c;可以使用一下命令…

STL好难(3):vector的使用

目录 1.vector的介绍和使用 2.vector的常见构造&#xff1a; 3.vector的遍历方式 &#x1f349;[ ] 下标 &#x1f349;通过迭代器进行访问&#xff1a; &#x1f349;范围for&#xff1a; 4.vector的迭代器 &#x1f349;begin 和 end &#x1f349;rbegin 和 rend …

【论文阅读】Densenet:Densely Connected Convolutional Networks 密集连接的卷积网络

文章目录 前言一、摘要二、网络架构2.1. densenet2.2. dense block2.3 与resnet对比2.4 pytorch代码 三.实验结果四.结论 前言 从今天开始总结一下之前看的一些深度学习相关的论文。 今天的这篇还是比较经典的论文&#xff1a;密集连接网络。在很多国内的硕士毕业论文里都出现…

XDP入门--eBPF程序实现网桥/二层交换机转发功能

本文目录 1、试验环境2、eBPF字节码源代码实现3、用户态应用层管理与控制程序的源代码实现4、编译与运行5、测试结果 我们在此文的进阶部分 或者 此文中已经描述了如何设置Linux网桥&#xff0c;并将多个以太接口加入网桥后实现一个最基本的二层交换机的二层交换转发功能。Linu…

如何在华为OD机试B卷中获得满分?Java实现【食堂供餐】一文详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

【剑指offer】数据结构——链表

目录 数据结构——字符串直接解【剑指offer】06. 从尾到头打印链表牛客力扣 【剑指offer】24. 反转链表【剑指offer】25. 合并两个排序的链表【剑指offer】35. 复杂链表的复制【剑指offer】52. 两个链表的第一个公共结点 特殊解——双指针【剑指offer】18. 删除链表的节点【剑指…

六级备考23天|CET-6|翻译技巧4|2013年官方样题|新年|9:45~11:00

目录 1 PRACTICE ANSWER 2 PRACTICE ANSWER 3 ​ PRACTICE ANSWER 4 PRACTICE ANSWER 5 PRACTICE ANSWER 6 ​ PRACTICE ANSWER ​​​​​​​ 答案整合​​​​​​​ 1 PRACTICE Chinese new year is the Chinese most important traditional festival, wh…

2023上半年软考系统分析师科目一整理-02

2023上半年软考系统分析师科目一整理-02 1. 安全2. 知识产权 1. 安全 对称加密算法中&#xff0c;由于加密解密都使用同样的密钥&#xff0c;所以密钥需要进行共享&#xff0c;故也被称共享密钥算法。 三重DES加密是使用2个DES密钥&#xff0c;进行多次操作来完成的&#xff…

Redis相关

Redis基本概念 一、Redis的持久化方式二、Redis的单机、主从、哨兵、集群Redis主从复制的原理 三、Redis分布式锁的实现四、缓存穿透 击穿 雪崩 一、Redis的持久化方式 1&#xff09;RDB方式 2&#xff09;AOF方式 二、Redis的单机、主从、哨兵、集群 单机的问题&#xf…

机器学习 | SVD奇异值分解

本文整理自哔哩哔哩视频&#xff1a;什么是奇异值分解SVD–SVD如何分解时空矩阵 &#x1f4da;奇异值分解是什么&#xff1f; M是原始矩阵&#xff0c;它可以是任意的矩阵&#xff0c;奇异值分解就是将它分解为三个矩阵相乘。U和V是方阵&#xff0c;∑是不规则矩阵&#xff0c;…

django组件552

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

618京东预售一般便宜多少?跟直接买有啥区别?

618京东预售一般便宜多少?跟直接买有啥区别? 京东作为消费者比较喜欢的电商购物平台之一&#xff0c;经常会推出促销打折的活动&#xff0c;以吸引用户到平台上购物。在这些大促活动中&#xff0c;平台会在预售环节设置专属的优惠&#xff0c;让消费者下单提前锁定这些折扣&a…

一、stable diffusion的发展史

一、stable diffusion的发展史 本文目标&#xff1a;学习交流 对于熟悉SD的同学&#xff0c;一起学习和交流使用过程中的技巧和心得。 帮助新手 帮助没有尝试过SD但又对它感兴趣的同学快速入门&#xff0c;并且能够独立生成以上效果图。 1.发展史介绍&#xff1a; 2015年的时候…

RepGhost 解析

paper&#xff1a;RepGhost: A Hardware-Efficient Ghost Module via Re-parameterization official implementation&#xff1a;https://github.com/chengpengchen/repghost 存在的问题 特征重用feature reuse是轻量网络设计中常用的一种技术&#xff0c;现有的方法通常使…

[元带你学: eMMC协议详解 10] Device 识别流程 与 中断模式

依JEDEC eMMC 5.1及经验辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 所在专栏 《元带你学: eMMC协议详解》 全文2700字&#xff0c;重点需掌握设备识别过程&#xff08;CMD1 -> CMD2 -> CMD3&#xff09;, 这很常用&#xff0c; 也是最容易出现异常的地方。其他…

Git进阶之代码回滚、合并代码、从A分支选择N次提交,合并到B分支【revert、merge、rebase、cherry-pick】

B站视频地址&#xff1a; https://www.bilibili.com/video/BV1KX4y1a7N9 Git学习文档&#xff1a;https://d9bp4nr5ye.feishu.cn/wiki/PeDPw3mm3iFA36k9td9cVeignsZ 在很长一段时间里&#xff0c;我对Git的操作只限于&#xff1a;提交代码&#xff0c;拉取代码&#xff0c;合…

研报精选230528

目录 【行业230528华金证券】传媒行业深度研究&#xff1a;AIGC最新应用与场景研究 【行业230528国海证券】电动船舶行业深度报告&#xff1a;绿色智能大势已至&#xff0c;驶向电化百亿蓝海 【行业230528华西证券】纺织服装行业周报&#xff1a;5月增长放缓无碍中长期出清逻辑…

Linux下的yum和vim

目录 一、Linux软件包管理器yum1.1 何为软件包&#xff1f;1.2 rzsz工具1.3 如何安装和卸载软件&#xff1f;1.4 Linux的软件生态 二、vim文本编辑器 一、Linux软件包管理器yum 1.1 何为软件包&#xff1f; 软件包可以理解成是windows下别人提前编译好的安装包程序&#xff0…