KM算法(运用篇)
最佳匹配
什么是完美匹配
如果一个二分图,X部和Y部的顶点数相等,若存在一个匹配包含X部与Y部的所有顶点,则称为完美匹配。
换句话说:若二分图X部的每一个顶点都与Y中的一个顶点匹配,**并且**Y部中的每一个顶点也与X部中的一个顶点匹配,则该匹配为完美匹配。
什么是完备匹配
如果一个二分图,X部中的每一个顶点都与Y部中的一个顶点匹配,**或者**Y部中的每一个顶点也与X部中的一个顶点匹配,则该匹配为完备匹配。
什么是最佳匹配
带权二分图的权值最大的完备匹配称为最佳匹配。
二分图的最佳匹配不一定是二分图的最大权匹配。
转化
可以添加一些权值为0的边,使得最佳匹配和最大权匹配统一起来。
KM算法
求二分图的最佳匹配有一个非常优秀的算法,可以做到O(N^3),这就是KM算法。该算法描述如下:
1.首先选择顶点数较少的为X部,初始时对X部的每一个顶点设置顶标,顶标的值为该点关联的最大边的权值,Y部的顶点顶标为0。
2.对于X部中的每个顶点,在相等子图中利用匈牙利算法找一条增广路径,如果没有找到,则修改顶标,扩大相等子图,继续找增广路径。当每个点都找到增广路径时,此时意味着每个点都在匹配中,即找到了二分图的完备匹配。该完备匹配即为二分图的最佳匹配。
什么是相等子图呢?因为每个顶点有一个顶标,如果我们选择边权等于两端点的顶标之和的边,它们组成的图称为相等子图。
如果从X部中的某个点Xi出发在相等子图中没有找到增广路径,我们是如何修改顶标的呢?如果我们没有找到增广路径,则我们一定找到了许多条从Xi出发并结束于X部的匹配边与未匹配边交替出现的路径,姑且称之为交错树。我们将交错树中X部的顶点顶标减去一个值d,交错树中属于Y部的顶点顶标加上一个值d。这个值后面要讲它如何计算。那么我们会发现:
两端都在交错树中的边(i,j),其顶标和没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
两端都不在交错树中的边(i,j),其顶标也没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
X端不在交错树中,Y端在交错树中的边(i,j),它的顶标和会增大。它原来不属于相等子图,现在仍不属于相等子图。
X端在交错树中,Y端不在交错树中的边(i,j),它的顶标和会减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
我们修改顶标的目的就是要扩大相等子图。为了保证至少有一条边进入相等子图,我们可以在交错树的边中寻找顶标和与边权之差最小的边,这就是前面说的d值。将交错树中属于X部的顶点减去d,交错树中属于Y部的顶点加上d。则可以保证至少有一条边扩充进入相等子图。
3.当X部的所有顶点都找到了增广路径后,则找到了完备匹配,此完备匹配即为最佳匹配。
相等子图的若干性质
- 在任意时刻,相等子图上的最大权匹配一定小于等于相等子图的顶标和。
- 在任意时刻,相等子图的顶标和即为所有顶点的顶标和。
- 扩充相等子图后,相等子图的顶标和将会减小。
- 当相等子图的最大匹配为原图的完备匹配时,匹配边的权值和等于所有顶点的顶标和,此匹配即为最佳匹配
以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶标,每次修改顶 标时由于要枚举边来求d值,复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数slack,每次开 始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与 A[i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修 改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d
模板一,用全局变量minz表示边权和顶标最小的差值,省去slack数组
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = + ;
const int INF = 0x3f3f3f3f; int wx[maxn], wy[maxn];//每个点的顶标值(需要根据二分图处理出来)
int cx[maxn], cy[maxn];//每个点所匹配的点
int visx[maxn], visy[maxn];//每个点是否加入增广路
int cntx, cnty;//分别是X和Y的点数
int Map[maxn][maxn];//二分图边的权值
int minz;//边权和顶标最小的差值 bool dfs(int u)//进入DFS的都是X部的点
{
visx[u] = ;//标记进入增广路
for(int v = ; v <= cnty; v++)
{
if(!visy[v] && Map[u][v] != INF)//如果Y部的点还没进入增广路,并且存在路径
{
int t = wx[u] + wy[v] - Map[u][v];
if(t == )//t为0说明是相等子图
{
visy[v] = ;//加入增广路 //如果Y部的点还未进行匹配
//或者已经进行了匹配,可以从原来的匹配反向找到增广路
//那就可以进行匹配
if(cy[v] == - || dfs(cy[v]))
{
cx[u] = v;
cy[v] = u;//进行匹配
return ;
}
}
else if(t > )//此处t一定是大于0,因为顶标之和一定>=边权
{
minz = min(minz, t);//边权和顶标最小的差值
}
}
}
return false;
} int KM()
{
memset(cx, -, sizeof(cx));
memset(cy, -, sizeof(cy));
memset(wx, , sizeof(wx));//wx的顶标为该点连接的边的最大权值
memset(wy, , sizeof(wy));//wy的顶标为0
for(int i = ; i <= cntx; i++)//预处理出顶标值
{
for(int j = ; j <= cnty; j++)
{
if(Map[i][j] == INF)continue;
wx[i] = max(wx[i], Map[i][j]);
}
}
for(int i = ; i <= cntx; i++)//枚举X部的点
{
while()
{
minz = INF;
memset(visx, , sizeof(visx));
memset(visy, , sizeof(visy));
if(dfs(i))break;//已经匹配正确 //还未匹配,将X部的顶标减去minz,Y部的顶标加上minz
for(int j = ; j <= cntx; j++)
if(visx[j])wx[j] -= minz;
for(int j = ; j <= cnty; j++)
if(visy[j])wy[j] += minz;
}
} int ans = ;//二分图最优匹配权值
for(int i = ; i <= cntx; i++)
if(cx[i] != -)ans += Map[i][cx[i]];
return ans;
}
int n, k;
int main()
{
while(scanf("%d", &n) != EOF)
{
for(int i = ; i <= n; i++)
{
for(int j = ; j <= n; j++)
scanf("%d", &Map[i][j]);
}
cntx = cnty = n;
printf("%d\n", KM());
}
return ;
}
模板二,用slack数组(对于完全图的优化很快)
和全局变量不同的是,全局变量在每次while循环中都需要赋值成INF,每次求出的是所有点的最小值,而slack数组在每个while外面就初始化好,每次while循环slack数组的每个值都在用到,一次增广路中求出的slack值会更准确,循环次数比全局变量更少
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = + ;
const int INF = 0x3f3f3f3f; int wx[maxn], wy[maxn];//每个点的顶标值(需要根据二分图处理出来)
int cx[maxn], cy[maxn];//每个点所匹配的点
int visx[maxn], visy[maxn];//每个点是否加入增广路
int cntx, cnty;//分别是X和Y的点数
int Map[maxn][maxn];//二分图边的权值
int slack[maxn];//边权和顶标最小的差值 bool dfs(int u)//进入DFS的都是X部的点
{
visx[u] = ;//标记进入增广路
for(int v = ; v <= cnty; v++)
{
if(!visy[v] && Map[u][v] != INF)//如果Y部的点还没进入增广路,并且存在路径
{
int t = wx[u] + wy[v] - Map[u][v];
if(t == )//t为0说明是相等子图
{
visy[v] = ;//加入增广路 //如果Y部的点还未进行匹配
//或者已经进行了匹配,可以从原来的匹配反向找到增广路
//那就可以进行匹配
if(cy[v] == - || dfs(cy[v]))
{
cx[u] = v;
cy[v] = u;//进行匹配
return ;
}
}
else if(t > )//此处t一定是大于0,因为顶标之和一定>=边权
{
slack[v] = min(slack[v], t);
//slack[v]存的是Y部的点需要变成相等子图顶标值最小增加多少
}
}
}
return false;
} int KM()
{
memset(cx, -, sizeof(cx));
memset(cy, -, sizeof(cy));
memset(wx, , sizeof(wx));//wx的顶标为该点连接的边的最大权值
memset(wy, , sizeof(wy));//wy的顶标为0
for(int i = ; i <= cntx; i++)//预处理出顶标值
{
for(int j = ; j <= cnty; j++)
{
if(Map[i][j] == INF)continue;
wx[i] = max(wx[i], Map[i][j]);
}
}
for(int i = ; i <= cntx; i++)//枚举X部的点
{
memset(slack, INF, sizeof(slack));
while()
{ memset(visx, , sizeof(visx));
memset(visy, , sizeof(visy));
if(dfs(i))break;//已经匹配正确 int minz = INF;
for(int j = ; j <= cnty; j++)
if(!visy[j] && minz > slack[j])
//找出还没经过的点中,需要变成相等子图的最小额外增加的顶标值
minz = slack[j];
//和全局变量不同的是,全局变量在每次while循环中都需要赋值成INF,每次求出的是所有点的最小值
//而slack数组在每个while外面就初始化好,每次while循环slack数组的每个值都在用到
//在一次增广路中求出的slack值会更准确,循环次数比全局变量更少 //还未匹配,将X部的顶标减去minz,Y部的顶标加上minz
for(int j = ; j <= cntx; j++)
if(visx[j])wx[j] -= minz;
for(int j = ; j <= cnty; j++)
//修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去minz
if(visy[j])wy[j] += minz;
else slack[j] -= minz;
}
} int ans = ;//二分图最优匹配权值
for(int i = ; i <= cntx; i++)
if(cx[i] != -)ans += Map[i][cx[i]];
return ans;
}
int n, k;
int main()
{
while(scanf("%d", &n) != EOF)
{
for(int i = ; i <= n; i++)
{
for(int j = ; j <= n; j++)
scanf("%d", &Map[i][j]);
}
cntx = cnty = n;
printf("%d\n", KM());
}
return ;
}
KM算法(运用篇)的更多相关文章
- KM算法(理解篇)
转载:https://www.cnblogs.com/logosG/p/logos.html(很好,很容易理解) 一.匈牙利算法 匈牙利算法用于解决什么问题? 匈牙利算法用于解决二分图的最大匹配问题. ...
- KM算法萌新讲解篇
KM算法 首先了解问题:也就是最大权值匹配: 二分图里,边带了权值,求整幅图里匹配最大/最小的权值 因为接触匈牙利算法的时候看的是找对象系列的博文,所以也自己写一发找对象的博文吧: 算法背景: 信 ...
- KM算法及其优化的学习笔记&&bzoj2539: [Ctsc2000]丘比特的烦恼
感谢 http://www.cnblogs.com/vongang/archive/2012/04/28/2475731.html 这篇blog里提供了3个链接……基本上很明白地把KM算法是啥讲清楚 ...
- 匈牙利算法、KM算法
PS:其实不用理解透增广路,交替路,网上有对代码的形象解释,看懂也能做题,下面我尽量把原理说清楚 基本概念 (部分来源.部分来源) 二分图: 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相 ...
- HDU-2255(KM算法)
HDU-2255 题目意思转化之后就是,给你一个二分图(也称 二部图) ,要求选择一些边让左边的点都对应左边的某一个点!该问题也叫做二分图最大匹配.所以可以用KM算法来做这道题.KM前提你要理解匈牙利 ...
- 二分图最大权完美匹配KM算法
KM算法二分图 KM求得二分图与普通二分图的不同之处在于:此二分图的每条边(男生女生)上都附了权值(好感度).然后,求怎样完美匹配使得权值之和最大. 这,不止一般的麻烦啊. 可以通过一个期望值来求. ...
- 二分图最大权匹配——KM算法
前言 这东西虽然我早就学过了,但是最近才发现我以前学的是假的,心中感慨万千(雾),故作此篇. 简介 带权二分图:每条边都有权值的二分图 最大权匹配:使所选边权和最大的匹配 KM算法,全称Kuhn-Mu ...
- 【7.18总结】KM算法
先贴代码,参考博客来源于:https://blog.csdn.net/zyy173533832/article/details/11519291#commentBox 例题:HDU 2255 题意:n ...
- KM 算法
KM 算法 可能需要先去学学匈牙利算法等二分图相关知识. 模板题-洛谷P6577 [模板]二分图最大权完美匹配 给 \(n\) 和 \(m\) 与边 \(u_i,v_i,w_i(1\le i\le m ...
随机推荐
- Entity Framework 6.0 Tutorials(4):Database Command Logging
Database Command Logging: In this section, you will learn how to log commands & queries sent to ...
- [GO]切片和底层数组的关系
package main import "fmt" func main() { a := [], , , , , , , , , } s1 := a[:] s1[] = fmt.P ...
- [GO]冒泡排序的原理和代码实现
冒泡排序的原理:对于一个数组里所有的元素进行两两比较,发生大于则变换数组下标则为升序排序,发生小于则变换数据下标的则为降序排序 比如给定的数组为[1, -2, 3, -4],对于我们的需求,两两比较后 ...
- Charles安装证书ssl proxying
1.找到工具栏上方的 help 按钮 2.help下面有一个 ssl proxying的选项,点击ssl proxying 选择里面的第三个:install charles root certific ...
- (回溯法)ip地址的合理性
题目: 给定一个只包含数字的字符串,通过返回所有可能有效的IP地址组合来恢复它. 例如: 给定“”, return [“255.255.11.135”,“255.255.111.35”]. (顺序无所 ...
- css总结11:css的overflow问题
1 排版时经常遇到块级元素内容overflow,怎么妥当处理是一个关键. overflow的常用属性: 代码: <!DOCTYPE html><html lang="en ...
- 使用dom解析器对xml文档内容进行增删查改
直接添代码: XML文档名称(one.xml) <?xml version="1.0" encoding="UTF-8" standalone=" ...
- Java 自定义异常类
类1:public class LogicException extends RuntimeException { //业务逻辑异常 /** * * @param mes ...
- JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(2):SSM+Redis概念理解
一.SSM+Redis的结构图 在Java互联网中,以Spring+SpringMVC+MyBatis(SSM)作为主流框架,SSM+Redis的结构图如下: 二.下面介绍它们各自承担的功能: 1.S ...
- Unity3D管网分析
给大家分享一下自己之前没事写的Unity3D的插件,主要用来对管网的搭建和分析, 开源在Github上 https://github.com/LizhuWeng/PipeNet,可以给需要的朋友做一个 ...