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

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

引入

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

西装笔挺,清瘦智慧。

金丝眼镜,黑色钢笔。

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

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

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

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

前置知识

链式前项星

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

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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智慧城市同城V4小程序V2.24独立开源版 + 全插件+VUE小程序开源前端+最新用户授权接口

智慧城市同城V4小程序V2.22开源独立版本月最新版&#xff0c;与上一版相比修复了一些小细节&#xff0c;功能本身并无大的变化。新版系统包含全插件、包括很多稀缺收费的插件都在里面如括招聘、 家政等&#xff0c;外加小程序的VUE开源前端&#xff0c;整个系统全开源&#xff…

机器学习 | 降维:PCA主成分分析

本文整理自 长路漫漫2021的原创博客&#xff1a;sklearn基础篇&#xff08;九&#xff09;-- 主成分分析&#xff08;PCA&#xff09;李春春_的原创博客&#xff1a;主成分分析&#xff08;PCA&#xff09;原理详解bilibili视频&#xff1a;用最直观的方式告诉你&#xff1a;什…

Python中模块的使用方法4

1 模块、包和库的区别 Python中&#xff0c;模块的英文是“module”&#xff0c;是一个以py为后缀名的文件&#xff1b;包的英文是“package”&#xff0c;是一个包含了多个模块的目录&#xff1b;库的英文是“library”&#xff0c;包含了具有相关功能的包和模块。 2 模块的…

web练习第二周

前言&#xff1a;&#xff08;博主个人学习笔记&#xff0c;不用看&#xff09;web练习第二周&#xff0c;仅做出前3题。相比于第一周&#xff0c;难度大幅增加&#xff0c;写题时就算看了wp还是像个无头苍蝇一样到处乱创&#xff0c;大多都是陌生知识点&#xff0c;工具的使用…

LeetCode刷题(ACM模式)-02链表

参考引用&#xff1a;代码随想录 注&#xff1a;每道 LeetCode 题目都使用 ACM 代码模式&#xff0c;可直接在本地运行&#xff0c;蓝色字体为题目超链接 0. 链表理论基础 0.1 链表定义 链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff1a…

矿井水除总氮工艺详解

一、项目概述 项目背景: 1、水资源浪费长期以来&#xff0c;采煤对地下水造成了严重破坏。绝大部分矿井水&#xff0c;被以直排方式&#xff0c;流入河道、田野&#xff0c;这不仅造成水资源的白白浪费&#xff0c;也污染了环境。社会对此反响强烈的同时&#xff0c;煤矿企业也…

Live800:客服系统知识库建设中需要注意的三个要点

互联网的快速发展&#xff0c;让客服行业也随之发生着巨大的变化。传统的客服方式越来越难以满足人们的需求&#xff0c;客户对客服的要求也变得越来越高。在这种情况下&#xff0c;客服系统成为了一种必不可少的工具。 客服系统作为企业与客户沟通的重要渠道&#xff0c;其之所…

电脑msvcp120.dll缺失怎么办?由于找不到msvcp120.dll的解决方案

MSVCP120.dll文件是Windows操作系统中的一种动态链接库文件。它是由Microsoft C软件包提供的重要组件。当系统提示“MSVCP120.dll文件缺失”时&#xff0c;可能会导致某些应用程序无法正常运行。 以下是修复MSVCP120.dll缺失问题的几种方法&#xff1a; 方法一&#xff1a;修复…

ChatGPT发展报告:原理、技术架构详解和产业未来(附下载)

今年12月1日&#xff0c;OpenAI推出人工智能聊天原型ChatGPT&#xff0c;再次赚足眼球&#xff0c;为AI界引发了类似AIGC让艺术家失业的大讨论。 据报道&#xff0c;ChatGPT在开放试用的短短几天&#xff0c;就吸引了超过 100 万互联网注册用户。并且社交网络流传出各种询问或…

机试打卡 -12 滑动窗口最大值(优先队列堆)

我的思路1&#xff1a;队列&#xff0c;每次 出队入队&#xff0c;记录1个队列中的最大值索引&#xff0c;超时。。。 class Solution:def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:nums_lenlen(nums)ans_list[]# 队列长度为kqueuenums[:k]# 队列最大…

花朵识别系统Python实现,深度学习卷积神经网络算法

一、背景 花朵识别系统&#xff0c;基于Python实现&#xff0c;深度学习卷积神经网络&#xff0c;通过TensorFlow搭建卷积神经网络算法模型&#xff0c;并对数据集进行训练最后得到训练好的模型文件&#xff0c;并基于Django搭建可视化操作平台。 在当今信息化社会&#xff0c…

我3年前写的博客,又被别人抄去发论文了,该论文整个正文部分几乎直接照抄我的博客

我想说每一篇原创博客都是作者的心血&#xff0c;有时候写一篇博客也许会花一天&#xff0c;甚至好几天的时间&#xff0c;尊重原创&#xff0c;营造好的环境&#xff0c;才有可能出现更多优质的博文&#xff0c;而不是到处都是抄来抄去的低质量水文。 前几天接到来自粉丝的私信…

如何通过CRM系统做好客户的分级分类

随着市场竞争的不断加剧&#xff0c;尤其是以客户为中心时代的到来&#xff0c;企业越来越注重客户的管理和服务。而CRM系统&#xff0c;作为企业客户管理的重要工具&#xff0c;其核心任务是对客户进行分级分类&#xff0c;以便更好地为客户提供定制化的服务。 客户之间的价值…

【C++】——模板(泛型编程+函数模板+类模板)

文章目录 1. 前言2. 泛型编程3. 函数模板3.1 函数模板的原理3.2 函数模板的实例化3.3 模板参数的匹配原则 4. 类模板4.1 类模板的实例化 5. 结尾 1. 前言 之前我们学习了函数重载&#xff0c;让我们在写相似函数的时候非常方便&#xff0c;但函数重载还有很多不足的地方&#…

【源码解析】Nacos配置热更新的实现原理

使用入门 使用RefreshScopeValue&#xff0c;实现动态刷新 RestController RefreshScope public class TestController {Value("${cls.name}")private String clsName;}使用ConfigurationProperties&#xff0c;通过Autowired注入使用 Data ConfigurationProperti…

如何从Ubuntu Linux中删除Firefox Snap?

Ubuntu Linux是一款广受欢迎的开源操作系统&#xff0c;拥有强大的功能和广泛的应用程序选择。默认情况下&#xff0c;Ubuntu提供了一种称为Snap的软件打包格式&#xff0c;用于安装和管理应用程序。Firefox是一款流行的开源网络浏览器&#xff0c;而Firefox Snap是Firefox的Sn…

f-stack的源码编译安装

DPDK虽然能提供高性能的报文转发&#xff08;安装使用方法见DPDK的源码编译安装&#xff09;&#xff0c;但是它并没有提供对应的IP/TCP协议栈&#xff0c;所以在网络产品的某些功能场景下&#xff08;特别是涉及到需要使用TCP协议栈的情况&#xff09;&#xff0c;比如BGP邻居…

9. Linux下实现简单的UDP请求

本文简单介绍了UDP传输层协议&#xff0c;并在Linux下实现简单的socket通讯 一、UDP UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是一种无连接的传输层协议&#xff0c;它不保证数据包的可靠性和顺序。UDP在IP协议的基础上增加了简单的差错…

Spring Authorization Server 系列(二)获取授权码

Spring Authorization Server 系列&#xff08;二&#xff09;获取授权码 概述获取授权码获取授权码的url逻辑解析匹配url参数解析 概述 Spring Authorization Server 是基于 OAuth2.1 和 OIDC 1.0 的。 只有 授权码&#xff0c;刷新token&#xff0c;客户端模式。 获取授权码…

Revit建模|Revit风管怎么绘制?

​绘制风管是机电工程重要的一环&#xff0c;对于不少刚接触Revit的小伙伴来说似乎还无从下手&#xff0c;今天就让小编来告诉大家在Revit中绘制风管的方法。 一、在Revit绘制风管 第一步&#xff1a;首先我们先在revit的界面中项目文件找到风管。 第二步&#xff1a;打开后我…