二分图带权最大匹配-KM算法详解

文章目录

    • 零、前言
    • 一、红娘再牵线
    • 二、二分图带权最大完备匹配
      • 2.1二分图带权最大匹配
      • 2.2概念
      • 2.3KM算法
        • 2.3.1交错树
        • 2.3.2顶标
        • 2.3.3相等子图
        • 2.3.4算法原理
        • 2.3.5算法实现
    • 三、OJ练习
      • 3.1奔小康赚大钱
      • 3.2Ants

零、前言

关于二分图:二分图及染色法判定-CSDN博客

关于二分图最大匹配:二分图最大匹配——匈牙利算法详解


一、红娘再牵线

红娘刚给上一批男女牵完线,便又遇到了3对男女(即3男3女),这次虽然比之前人少但也没那么好对付,每个男生都对若干个女生都有意思,每个女生也都对若干个男生都有意思,但是每对男女间的好感度不同,固然可以很容易配成3对情侣,但很难让每个人都满意,所以他们来请红娘出出主意。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

红娘了解了每对男女间的好感度后思索了一会便有了主意,她找来三个男生要求他们根据对三个女生亲密度给出对理想对象的期望值,要求给出的期望值不得小于其对任何一个女生的亲密度,女生那边则先不给出期望值。三个男生有些不好意思,便都只给出了和三个女生中的最大亲密度为期望值,于是又有了这样一张图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

红娘打的什么算盘呢?她要让每个人都能有对象的情况下使得最终配对的情侣的亲密度之和最大,这是尽可能使每个人都满意的最好结局了。她要让最终每对情侣的期望值之和都等于其亲密值。

得到了男生们给出的期望值后,她先去给期望值之和恰好等于亲密度的男女配对(红线代表配对),男一和女三成功牵线:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

到了男二,发现符合条件的只有女三(期望值之和为亲密度),但是女三已经配对了,于是红娘让男一男二期望值都降低1,女三期望值提高1,如此一来男一女三仍符合条件,男二和女一也符合条件了,于是男儿女一牵线成功:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在只剩男三落单了,对于她而言,只有女三有好感,但是二者期望值不符合条件,于是红娘先让男三降低1期望值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接着让男一、男二、男三都降低1期望值,女一、女三都提高1期望值,这样男一和女一符合条件可以配对,男二和女二符合条件可以配对,男三和女三符合条件可以配对:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样一来大家都有了归宿,且配对的男女的期望值之和恰为亲密度。显然,这是大家最满意的结局。

二、二分图带权最大完备匹配

2.1二分图带权最大匹配

给定一张二分图,二分图的每条边都带有一个权值。求出该二分图的一组最大匹配,使得匹配边的权值总和最大。这个问题称为二分图的带权最大匹配,也称二分图最优匹配。注意,二分图带权最大匹配的前提是匹配数最大然后再最大化匹配边的权值总和

2.2概念

如果一个二分图的带权最大匹配还是一个完备匹配,那么我们称其为二分图带权最大完备匹配,如引例”红娘再牵线“就是一个二分图带权最大完备匹配的例子。

二分图带权最大完备匹配是二分图带权最大匹配的子问题,能解决二分图带权最大匹配自然能解决二分图带权最大完备匹配。

对于二分图带权最大匹配我们通用方法是费用流,其自然也可以解决二分图带权最大完备匹配,也是我们最常用的方法,当然,对于二分图带权最大完备匹配有一专门解决方法——KM算法

2.3KM算法

KM算法是对匈牙利算法的改造,我们从匈牙利算法入手,分析如何改造得以求解二分图带权最大完备匹配问题

2.3.1交错树

在匈牙利算法中,如果从某个左部节点出发寻找匹配失败,那么在DFS的过程中,所有访问过的节点,以及为了访问这些节点而经过的边,共同构成一棵树

这棵树的根是一个左部节点,所有叶子节点也都是左部节点(因为最终匹配失败了),并且树上第1,3,5,… 层的边都是非匹配边,第2,4,6,…层的边都是匹配边。因此,这棵树被称为交错树

2.3.2顶标

亦即”顶点标记值“,在二分图中,给第i(1 <= i <= n)个左部节点一个整数值la[i],给第j(1 <= j <= n)个右部节点一个整数值lb[j]。同时满足:∀i,j,la[i] + lb[j] >= w[i][j],其中w[i][j]为第i个左部节点和第j个右部节点之间的边权(没有边权时设为负无穷),这些整数值la[i]、lb[j]称为节点的顶标。

2.3.3相等子图

二分图中所有节点满足la[i] + lb[j] = w[i][j]的边构成的子图,称为二分图的相等子图

定理:

若相等子图中存在完备匹配,则这个完备匹配就是二分图的带权最大匹配。

证明十分容易:

在相等子图中,完备匹配的边权之和等于Σ(la[i] + lb[j]),即所有顶标之和。因为顶标满足Vi,j, la[i] + lb[j] ≥ w(i,j),所以在整个二分图中,任何一组匹配的边权之和都不可能大于所有顶标的和。

2.3.4算法原理

KM算法的基本思想就是,先在满足∀i,j, la[i] + lb[j] ≥ w[i][j]的前提下,给每个节点随意赋值一个顶标, 然后采取适当策略不断扩大相等子图的规模直至相等子图存在完备匹配。例如,我们可以赋值la[i] = max(w[i][j]),lb[j] = 0

对于一个相等子图,我们用匈牙利算法求它的最大匹配。若最大匹配不完备,则说明一定有一个左部节点匹配失败。该节点匹配失败的那次DFS形成了一棵交错树,记为T。

我们要找到相等子图中的完备匹配,此时失败说明相等子图中的边没有全部包含进来,所以我们要对顶标进行调整使得相等子图得到扩充

对于交错树中的边无非两种:

  • la[i] + lb[j] = w[i][j]的匹配边(这也是和匈牙利不同的一点),那么对于匹配边我们不需要修改顶标和,可以使得左边减少Δ,右边增加Δ
  • la[i] + lb[j] > w[i][j]的非匹配边(因为顶标和不小于权值,所以只能是大于),我们通过使左部节点减小Δ来减小顶标和,从而逼近权值

那么如何取Δ呢?

Δ为min(la[i] + lb[j] - w[i][j]),w<i , j>为非匹配边,那么每次左部根节点匹配失败,进行一次调整都会使得相等子图增加至少一条边,而又不减少相等子图中的边。

2.3.5算法实现
  • 初始化la,lb
  • 对每个左部点进行修改后的匈牙利算法,找左部点的匹配右部点
    • 匹配失败,就根据delta进行调整,再次匹配
  • 对于匈牙利算法的修改:
    • 只挑选顶标和为权值的边作为匹配边
    • 利用顶标和大于权值的边维护delta

时间复杂度:O(N^4)

代码实现:

#define N 110
int w[N][N];       // 边权
int la[N], lb[N];  // 左、右部点顶标
bool va[N], vb[N]; // 左、右部点是否在交错树中
int match[N];      // 右部点的匹配点
int n, delta;
bool dfs(int u)
{va[u] = true; // 在交替树中for (int v = 1; v <= n; v++)if (!vb[v])if (la[u] + lb[v] - w[u][v] == 0){vb[v] = true; // 进入交替树if (!match[v] || dfs(match[v])){match[v] = u;return true; // 找到增广路}}else// 维护delta,同时避免非匹配边右部点进入交替树,保证非匹配边只有左部点顶标减小delta = min(delta, la[u] + lb[v] - w[u][v]);return false;
}int KM()
{memset(match, 0, sizeof(match)), memset(lb, 0, sizeof(lb));for (int i = 1; i <= n; i++){la[i] = w[i][1];for (int j = 2; j <= n; j++)la[i] = max(la[i], w[i][j]);}for (int i = 1; i <= n; i++)while (true) // 直到找到匹配{memset(va, 0, sizeof(va)), memset(vb, 0, sizeof(vb));delta = 1 << 30; // infif (dfs(i))break;for (int j = 1; j <= n; j++) // 修改顶标,扩充相等子图{if (va[j])la[j] -= delta;if (vb[j])lb[j] += delta;}}int ans = 0;for (int i = 1; i <= n; i++)ans += w[match[i]][i];return ans;
}

三、OJ练习

3.1奔小康赚大钱

Problem - 2255 (hdu.edu.cn)

很直白的KM算法板子题,直接跑板子即可

#include <iostream>
#include <cmath>
#include <cstring>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include <cmath>
using namespace std;
using pii = pair<int, int>;
#define sc scanf
#define N 310
#define int long long
#define precision 1e-9int w[N][N];       // 边权
int la[N], lb[N];  // 左、右部点顶标
bool va[N], vb[N]; // 左、右部点是否在交错树中
int match[N];      // 右部点的匹配点
int n, delta;
bool dfs(int u)
{va[u] = 1; // 在交替树中for (int v = 1; v <= n; v++)if (!vb[v])if (la[u] + lb[v] - w[u][v] == 0){vb[v] = 1; // 进入交替树if (!match[v] || dfs(match[v])){match[v] = u;return true; // 找到增广路}}elsedelta = min(delta, la[u] + lb[v] - w[u][v]);return false;
}int KM()
{memset(match, 0, sizeof(match)), memset(lb, 0, sizeof(lb));for (int i = 1; i <= n; i++){la[i] = w[i][1];for (int j = 2; j <= n; j++)la[i] = max(la[i], w[i][j]);}for (int i = 1; i <= n; i++)while (true) // 直到找到匹配{memset(va, 0, sizeof(va)), memset(vb, 0, sizeof(vb));delta = 1 << 30; // infif (dfs(i))break;for (int j = 1; j <= n; j++) // 修改顶标,扩充相等子图{if (va[j])la[j] -= delta;if (vb[j])lb[j] += delta;}}int ans = 0;for (int i = 1; i <= n; i++)ans += w[match[i]][i];return ans;
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);//freopen("in.txt", "r", stdin);while (cin >> n){for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)cin >> w[i][j];cout << KM() << '\n';}return 0;
}

3.2Ants

3565 – Ants (poj.org)

一眼看去可能觉得要用到什么计算几何的知识,实际上如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由三角形性质可知AD+BC > AB + CD,所以对于一对交叉的边一定可以转化为一对不交叉的边并且权值和变小

所以问题等价为求二分图带权 最小 完备匹配

那么我们把两两黑白点之间距离求出然后取反跑BMKM即可

但是对于这道题直接跑我们的板子会TLE的,因为我们最坏O(N^4)的时间复杂度在这道题刚好被卡了。

有一种比较懒省事的优化方法就是把全局的delta换成一个数组slack,来记录访问到的非匹配边的顶标和和边权之差

好处是slack只用在while循环外初始化一次然后就能用到while结束,而全局变量每次都要从inf开始

但这是个假优化,最坏仍为O(N^4)

正确优化为O(N^3)的方法是bfs优化,优化角度为从每次加入相等子图的那条边接着找增广路,这就需要我们记录上次失败时交错树的某些信息了。这里只给出slack假优化代码,bfs优化有兴趣可以查相关资料、博客,主要太懒感觉没什么用

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cmath>
using namespace std;
#define int long longconst double inf = 1e30;const double eps = 1e-6;const int N = 110;int n;pair<int, int> ant[N], tree[N];double dis(int i, int j)
{return sqrt(1.0 * (tree[i].first - ant[j].first) * (tree[i].first - ant[j].first) + (tree[i].second - ant[j].second) * (tree[i].second - ant[j].second));
}double la[N], lb[N], w[N][N];bool va[N], vb[N];int match[N];double upd[N];bool dfs(int u)
{va[u] = 1; // 在交替树中for (int v = 1; v <= n; v++)if (!vb[v])if (la[u] + lb[v] - w[u][v] < eps){vb[v] = 1; // 进入交替树if (!match[v] || dfs(match[v])){match[v] = u;return true; // 找到增广路}}elseupd[v] = min(upd[v], la[u] + lb[v] - w[u][v]);return false;
}void KM()
{memset(match, 0, sizeof(match)), memset(lb, 0, sizeof(lb));for (int i = 1; i <= n; i++){la[i] = w[i][1];for (int j = 2; j <= n; j++)la[i] = max(la[i], w[i][j]);}for (int i = 1; i <= n; i++){memset(upd, 0x3f, sizeof(upd));while (1) // 直到左部点找到匹配{memset(va, 0, sizeof(va)), memset(vb, 0, sizeof(vb));for (int i = 1; i <= n; i++)upd[i] = inf;if (dfs(i))break;double delta = inf;for (int j = 1; j <= n; j++)if (!vb[j])delta = min(delta, upd[j]);for (int j = 1; j <= n; j++) // 修改顶标{if (va[j])la[j] -= delta;if (vb[j])lb[j] += delta;}}}for (int i = 1; i <= n; i++)cout << match[i] << '\n';
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);while (cin >> n){for (int i = 1; i <= n; i++)cin >> ant[i].first >> ant[i].second;for (int i = 1; i <= n; i++)cin >> tree[i].first >> tree[i].second;for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)w[i][j] = -dis(i, j);KM();}return 0;
}

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

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

相关文章

企业销售获客难?分享一个精准筛查企业客户的技巧

作为企业销售经理&#xff0c;曾经一直让我们很头疼的问题之一就是获客困难。回想起以往&#xff0c;我们需要通过各种手段&#xff0c;手动查找电话名单、网络搜索到各种渠道&#xff0c;费尽心思的去筛查才能找到潜在客户。获客流程长还耗费很多的精力&#xff0c;拿到手的客…

面试宝典进阶之Java线程面试题

T1、【初级】线程和进程有什么区别&#xff1f; &#xff08;1&#xff09;线程是CPU调度的最小单位&#xff0c;进程是计算分配资源的最小单位。 &#xff08;2&#xff09;一个进程至少要有一个线程。 &#xff08;3&#xff09;进程之间的内存是隔离的&#xff0c;而同一个…

源码搭建教学:连锁餐饮APP开发实战

连锁餐饮APP&#xff0c;对于很多从事餐饮行业的人来说不会陌生&#xff0c;同样这个项目本身就有着很高的热度。今天&#xff0c;小编将深入为大家讲述一下此系统的前后端开发、数据库设计、用户界面设计等方面&#xff0c;让您深入了解全栈开发的方方面面。 一、项目准备与规…

一、QT的前世今

一、Qt是什么 1、Qt 是一个1991年由奇趣科技开发的跨平台C图形用户界面应用程序开发框架。它既可以开发GUI程序&#xff0c;也可用于开发非GUI程序&#xff0c;比如控制台工具和服务。 2、Qt是面向对象的框架&#xff0c;具有面向对象语言的特性&#xff1a;封装、继承、多态。…

Unity2022.3打包Android后从AB包加载场景发现丢失大量脚本问题

问题 这两天遇到一个问题&#xff0c;在VR项目打包Android的时候&#xff0c;加载场景后&#xff0c;Timeline工作不正常&#xff0c;找不到原因。 现象 看到有很多警告&#xff0c;丢失脚本的Log。 因为场景本身也有一些丢失的脚本所以没在意&#xff0c;但是又不是所有脚本…

6.3、SDN在云计算中的应用

目录 一、SDN概念 1.1、传统网络机制 1.2、SDN网络机制 1.3、二者区别 1.4、SDN架构 二、云数据中心 2.1、公有云环境特点 2.2、两大挑战 2.3、云数据中心引入SDN技术解决两大挑战 三、SDN云计算解决方案 3.1、SDN云计算解决方案之控制平面openflow协议 3.1.…

Android studio调试

Android Studio连接手机详细教程(包含遇到的问题集)_android studio 连接手机-CSDN博客 可以创建虚拟机或直连真机或直连模拟器。 无法打开本地终端 Android studio Failed to start [powershell.exe] 利用Android studio的adb命令删除app应用 - 简书 利用ADB工具免root停用A…

深入理解 go chan

go 里面&#xff0c;在实际程序运行的过程中&#xff0c;往往会有很多协程在执行&#xff0c;通过启动多个协程的方式&#xff0c;我们可以更高效地利用系统资源。 而不同协程之间往往需要进行通信&#xff0c;不同于以往多线程程序的那种通信方式&#xff0c;在 go 里面是通过…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-2 常用表单控件

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>常用表单控件</title> <style> form {width: 260px;margin: 0 auto;border: 1px solid #ccc;padding: 20px; } .right {float: right; } </style&g…

Windows系统搭建WebDAV服务并结合内网穿透实现公网访问本地文件

文章目录 前言1. 安装IIS必要WebDav组件2. 客户端测试3. 使用cpolar内网穿透&#xff0c;将WebDav服务暴露在公网3.1 安装cpolar内网穿透3.2 配置WebDav公网访问地址 4. 映射本地盘符访问 前言 在Windows上如何搭建WebDav&#xff0c;并且结合cpolar的内网穿透工具实现在公网访…

阿尔泰科技——PXIe8912/8914/8916高速数据采集卡

阿尔泰科技PXIe8912/8914/8916高速数据采集卡是2通道同步采样数字化仪&#xff0c;专为输入信号高达 100M 的高频和高动态范围的信号而设计。 与Labview无缝连接&#xff0c;提供图形化API函数。模拟输入范围可以通过软件编程设置为1V 或者5V。配备了容量高达 2GB的板载内存。…

亚马逊实时 AI 编程助手 CodeWhisperer使用体验

文章目录 1&#xff1a;什么是CodeWhisperer &#xff1f;2&#xff1a;试用3&#xff1a;上手体验 1&#xff1a;什么是CodeWhisperer &#xff1f; 最近ChatGPT展现出强大AI能力给我们带来了深刻的影响&#xff0c;AI现在不是一个概念&#xff0c;基于AI的产品一定在各行各业…

Elasticsearch 地理空间搜索 - 远超 OpenSearch

作者&#xff1a;来自 Elastic Nathan_Reese 2021 年&#xff0c;OpenSearch 和 OpenSearch Dashboards 开始作为 Elasticsearch 和 Kibana 的分支。 尽管 OpenSearch 和 OpenSearch Dashboards 具有相似的血统&#xff0c;但它们不提供相同的功能。 在分叉时&#xff0c;只能克…

喜好儿AI周报Weekly(第9期)CES2024 AI产业大爆发 | Rabbit R1 | 3D-Fauna | OLED屏幕 | Genie | MagicVideoV2 | Magnific

各位观众朋友们大家好&#xff01;我是被老板派去出差逛CES2024 拉斯维加斯消费电子展差点迷路回不来的阿喜。一起去看看这一周有什么新鲜事吧。 本期导读&#xff1a; 逛逛CES 2024消费电子展Rabbit R1人工智能设备三星AI机器人BallieLG无线透明OLED屏幕Portalgraph VR空间投…

jmeter和meterSphere如何使用第三方jar包

引用jar包语言使用的都是beanshell 问题起因&#xff1a;metersphere 接口自动化实现过程中&#xff0c;如何实现字符串加密且加密方法依赖第三方库&#xff1b; 使用语言&#xff1a;beanshell脚本语言&#xff0c;java语言 使用工具&#xff1a;idea jmeter metersphere 1.…

如何分析测试任务及需求(附分析流程)

测试分析 确认测试范围 根据测试项目的不同需求&#xff0c;有大致几类测试项目类型&#xff1a;商户/平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析&#xff0c;并给出对应的输出…

【论文阅读 CIDR17】Self-Driving Database Management Systems

Self-Driving Database Management Systems MySummary ABSTRACT 之前的advisory tools来帮助DBA处理系统调优和物理设计的各个方面&#xff0c;都仍然需要人类对数据库的任何更改做出最终决定&#xff0c;并且是在问题发生后修复问题的反动措施reactionary measures 。 An …

Linux进程【2】进程地址空间(+页表详解哦)

fork 引言&#xff08;程序地址空间&#xff09;进程地址空间进程地址空间mm_struct 虚拟地址到物理地址的转化总结 引言&#xff08;程序地址空间&#xff09; 在之前的学习过程中&#xff0c;我们认识了内存与地址&#xff0c;并且了解了在程序地址空间中的基本分区&#xf…

three.js 使用 tweenjs绘制相机运动动画

效果&#xff1a; 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div class"box-right"…

SpringBoot默认配置文件

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:SpringBoot默认配置文件 📚个人知识库: Leo知识库,欢迎大家访问 1.前言☕…