(写给自己看)匈牙利算法(最大匹配)和KM算法(最佳匹配)

匈牙利算法

思想

  • 不断寻找增广路,每次寻得增广路,交换匹配边和非匹配边,则匹配点数+1
  • 这里增广路含义:交错路,即从未匹配点出发经过未匹配边->匹配边->未匹配边->.....->未匹配边
  • Konig定理:无权二分图的最大匹配=最小覆盖点集,证明
  • 有价值的博客:blog1,blog2,blog3
  • 算法其实并不难

模板

模板题目:poj1274,poj1325,后者需要konig定理转化

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
using namespace std;
#define ll long long
#define name2str(name) (#name)
#define db(x) cout<<#x"=["<<(x)<<"]"<<endl
#define CL(a,b) memset(a,b,sizeof(a))
#define sf(a) scanf("%d",&a)
#define pr(a) printf("%d\n",a)
#define rng(a) a.begin(),a.end()
#define pb push_back
#define fast ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fr0(i,m) for(int i=0;i<m;i++)
#define fr1(i,m) for(int i=1;i<=m;i++)
//author:fridayfang
//date:19 3月 12
const double esp=1e-8;
const int mod=1e9+7;
const double pi=acos(-1);
const int inf=0x3f3f3f3f;
const int maxn = 200 + 5;
const int maxm = 1e6+5;
int n,m;
int mpp[maxn][maxn];//mp[i][j] means cow i like stall j
int used[maxn];//used[i] for cow i
int linker[maxn];//linker[i] for stall if -1 not match
//used[i],linker[i],i表示的是右边的点
int dfs(int u){// 实际运行dfs()都是左边的点
for(int i=1;i<=m;i++){
if(mpp[u][i]&&!used[i]){
used[i]=1;
if(linker[i]==-1||dfs(linker[i])){
linker[i]=u;
return 1;
}
}
}
return false;
}
int main(){
while(~scanf("%d %d",&n,&m)){
CL(mpp,0);
fr1(i,n){
int num,v;sf(num);
fr1(j,num){sf(v);mpp[i][v]=1;} }
//build ok
int ans=0;
CL(linker,-1);
for(int i=1;i<=n;i++){
CL(used,0);
if(dfs(i)) ans++;//每次dfs最多指增加一个点i被匹配,原先已经匹配的点依旧被匹配只是边有变化
}
pr(ans);
} return 0;
}

KM算法求带权图的最佳匹配

思想

  • 设计了顶标的思路,每次在dfs(u)失败后,通过改变增广路上的点的顶标lx[],ly[],来加入次大的边
  • 一个边被加入<=>\(lx[u]+ly[v]==mp[u][v]\)
  • 顶标变化d,根据满足visx[i]&&!visy[j]的点对来确定,\(d=min(d,lx[i]+ly[j]-mp[i][j])\)
  • 有O(N3)和O(n4)写法,后者其实也不是很慢;前者实现一般需要slack[]数组,需要在dfs(u)和顶标变化的时候维护,具体可见代码
  • 有价值的blog,blog1,blog2,blog3

模板

  • hdu2255模板题
  • code1 (O(n^4))(我常用的模板,也比较好写)
#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize("O3")
#define ll long long
#define ull unsigned long long
#define name2str(name) (#name)
#define db(x) cout<<#x"=["<<(x)<<"]"<<endl
#define CL(a,b) memset(a,b,sizeof(a))
#define sf(a) scanf("%d",&a)
#define pr(a) printf("%d\n",a)
#define rng(a) a.begin(),a.end()
#define pb push_back
#define fast ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fr0(i,m) for(int i=0;i<m;i++)
#define fr1(i,m) for(int i=1;i<=m;i++)
//author:fridayfang
//date:19 3月 12
const double esp=1e-8;
const int mod=1e9+7;
const double pi=acos(-1);
const int inf=0x3f3f3f3f;
const int maxn = 300 + 5;
const int maxm = 1e6+5;
//先试试O(n^4)写法
int lx[maxn],ly[maxn];
int mat[maxn][maxn];
int n;
int visx[maxn],visy[maxn];
int linker[maxn]; bool dfs(int u){
visx[u]=1;
for(int i=1;i<=n;i++){
if(!visy[i]&&(lx[u]+ly[i]==mat[u][i])){
visy[i]=1;
if(linker[i]==-1||dfs(linker[i])){
linker[i]=u;return true;
}
}
}
return false;
}
int km(){
CL(linker,-1),CL(lx,0),CL(ly,0);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
lx[i]=max(lx[i],mat[i][j]);
}
}
for(int i=1;i<=n;i++){
while(true){
CL(visx,0),CL(visy,0);
if(dfs(i))break;
int d=inf;
for(int x=1;x<=n;x++){
for(int y=1;y<=n;y++){
if(visx[x]&&!visy[y])d=min(d,lx[x]+ly[y]-mat[x][y]);
}
}
for(int i=1;i<=n;i++){
if(visx[i])lx[i]-=d;
if(visy[i])ly[i]+=d;
}
}
}
int res=0;
for(int i=1;i<=n;i++){
res+=mat[linker[i]][i];
}
return res;
} int main(){
while(~sf(n)){
fr1(i,n)fr1(j,n)sf(mat[i][j]);
int ans=km();
pr(ans);
} return 0;
}
  • code2 (O(n^3))
/*
实际上,O(n^4)的KM算法表现不俗,使用O(n^3)并不会很大的提高KM的运行效率
需要在O(1)的时间找到任意一条边,使用邻接矩阵存储更为方便
*/
#include <cstring>
#include <cstdio>
const int maxn = 305;
const int INF = 0x3f3f3f3f;
int match[maxn],lx[maxn],ly[maxn],slack[maxn];
int G[maxn][maxn];
bool visx[maxn],visy[maxn];
int n,nx,ny,ans; bool findpath(int x)
{
int tempDelta; visx[x] = true;
for(int y = 0 ; y < ny ; ++y){
if(visy[y]) continue;
tempDelta = lx[x] + ly[y] - G[x][y];
if(tempDelta == 0){//(x,y)在相等子图中
visy[y] = true;
if(match[y] == -1 || findpath(match[y])){
match[y] = x;
return true;
}
}
else if(slack[y] > tempDelta)
slack[y] = tempDelta;//(x,y)不在相等子图中且y不在交错树中
}
return false;
}
void KM()
{ for(int x = 0 ; x < nx ; ++x){
for(int j = 0 ; j < ny ; ++j) slack[j] = INF;//这里不要忘了,每次换新的x结点都要初始化slack
while(true){
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));//这两个初始化必须放在这里,因此每次findpath()都要更新
if(findpath(x)) break;
else{
int delta = INF;
for(int j = 0 ; j < ny ; ++j)//因为dfs(x)失败了所以x一定在交错树中,y不在交错树中,第二类边
if(!visy[j] && delta > slack[j])
delta = slack[j];
for(int i = 0 ; i < nx ; ++i)
if(visx[i]) lx[i] -= delta;
for(int j = 0 ; j < ny ; ++j){
if(visy[j])
ly[j] += delta;
else
slack[j] -= delta;
//修改顶标后,要把所有的slack值都减去delta
//这是因为lx[i] 减小了delta
//slack[j] = min(lx[i] + ly[j] -w[i][j]) --j不属于交错树--也需要减少delta,第二类边
}
}
}
}
}
void solve()
{ memset(match,-1,sizeof(match));
memset(ly,0,sizeof(ly));
for(int i = 0 ; i < nx ; ++i){
lx[i] = -INF;
for(int j = 0 ; j < ny ; ++j)
if(lx[i] < G[i][j])
lx[i] = G[i][j];
}
KM();
}
int main()
{
while(scanf("%d",&n) != EOF){
nx = ny = n;
for(int i = 0 ; i < nx ; ++i)
for(int j = 0 ; j < ny ; ++j)
scanf("%d",&G[i][j]);
solve();
int ans = 0;
for(int i = 0 ; i < ny ; ++i)
if(match[i] != -1)
ans += G[match[i]][i];
printf("%d\n",ans);
}
return 0;
}

匈牙利&&EK算法(写给自己看)的更多相关文章

  1. 最大网络流 EK 算法

    网络流是什么类型的问题,看一道题目你就知道了 点击打开链接 . 默认具备图论的基本知识,网络流概念比较多,先看看书熟悉一下那些概念.比较好!一个寄出的网络最大流.EK算法写的. 这是一幅网络,求S   ...

  2. POJ 1459 网络流 EK算法

    题意: 2 1 1 2 (0,1)20 (1,0)10 (0)15 (1)20 2 1 1 2 表示 共有2个节点,生产能量的点1个,消耗能量的点1个, 传递能量的通道2条:(0,1)20 (1,0) ...

  3. 【算法•日更•第三十五期】FF算法优化:EK算法

    ▎写在前面 FF算法传送门 之前我们已经学过了FF算法(全称Ford-Fulkerson算法)来找最大流,但是这种算法仍有诸多不对的地方. 其实这种算法存在着严重的效率的问题,请看下面的图: 以这个图 ...

  4. vector实现最大流EK算法

    序: 在之前的文章中实现了不利用STL实现EK算法,效率也较高.这次我们企图简化代码,减少变量的使用与手写模拟的代码. 注意:vector等STL的container在不开O2优化的时候实现同一个效果 ...

  5. 二分图的最大匹配——最大流EK算法

    序: 既然是个图,并且求边数的最大值.那么这就可以转化为网络流的求最大流问题. 只需要将源点与其中一子集的所有节点相连,汇点与另一子集的所有节点相连,将所有弧的流量限制置为1,那么最大流 == 最大匹 ...

  6. 【读书笔记】《写给大忙人看的Java SE 8》——Java8新特性总结

    虽然看过一些Java 8新特性的资料,但是平时很少用到,时间长了就忘了,正好借着Java 9的发布,来总结下一些Java 8中的新特性. 接口中的默认方法和静态方法 先考虑一个问题,如何向Java中的 ...

  7. 网络最大流算法—EK算法

    前言 EK算法是求网络最大流的最基础的算法,也是比较好理解的一种算法,利用它可以解决绝大多数最大流问题. 但是受到时间复杂度的限制,这种算法常常有TLE的风险 思想 还记得我们在介绍最大流的时候提到的 ...

  8. POJ3436 ACM Computer Factory【EK算法】

    题意: 每个电脑需要P个组成部分,现有N的机器,每个机器都可以对电脑进行加工,不过加工的前提是某些部分已经存在,加工后会增加某些部分.且在单位时间内,每个机器的加工都有一个最大加工容量,求能得到的最大 ...

  9. 【最大流之ek算法】HDU1532 求最大流

    本来是继续加强最短路的训练,但是遇到了一个最短路 + 最大流的问题,最大流什么鬼,昨天+今天学习了一下,应该对ek算法有所了解,凭借学习后的印象,自己完成并ac了这个最大流的模板题 题目大意:都是图论 ...

随机推荐

  1. 【[Offer收割]编程练习赛10 C】区间价值

    [题目链接]:http://hihocoder.com/problemset/problem/1483 [题意] 中文题 [题解] 二分最后的答案; 二分的时候; 对于每一个枚举的值x; 计算小于等于 ...

  2. poj 3041 最小点覆盖=最大匹配

    #include<stdio.h> #include<string.h> #define  N  510 int map[N][N],n,mark[N],link[N]; in ...

  3. Oracle-表更名、转存数据

    --更名 ALTER TABLE T_LOGSRV_SERVICE RENAME TO T_LOGSRV_SERVICE_20170418_BAK; --创建同样的表 ;

  4. TortoiseGit生成PuttyKey与GitHub的SSH进行关联

    1.打开Puttygen 要到进度条满格为止,知道出现如下界面: 把上面的Key复制. 最后点击[Save private key]保存. 2.登录GitHub进行如下操作: Settings-> ...

  5. Android 最新面试题

    1. Intent的几种有关Activity启动的方式有哪些,你了解每一个含义吗? Intent的一些标记有FLAG_ACTIVITY_BROUGHT_TO_FRONT .FLAG_ACTIVITY_ ...

  6. 原型设计模式prototype-构造js自己定义对象

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. 《从零開始学Swift》学习笔记(Day 56)—— Swift编码规范之命名规范

    原创文章,欢迎转载.转载请注明:关东升的博客 程序代码中到处都是自定义的名字,取一个有样而且符合规范的名字非常重要. 命名方法非常多,可是比較有名的,广泛接受命名法有: 匈牙利命名,一般仅仅是命名变量 ...

  8. 安装eclipse maven插件m2eclipse No repository found containing

    m2eclipse插件是Eclipse的一款Maven插件. 安装m2eclipse插件的步骤例如以下: 启动Eclipse,在菜单条中选择Help,然后选择Install New Software- ...

  9. yolo源码解析(3):视频检测流程

    代码在自己电脑中!!!!不在服务器 根据前文所说yolo代码逻辑: ├── examples │ ├── darknet.c(主程序) │ │── xxx1.c │ └── xxx2.c │ ├── ...

  10. Floyed理解

    Floyed理解 Floyd算法的本质是动态规划,其转移方程为:f(k,i,j) = min( f(k-1,i,j), f(k-1,i,k)+f(k-1,k,j) ). f(k-1,i,j)表示经过前 ...