(写给自己看)匈牙利算法(最大匹配)和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;
}

最新文章

  1. Git-Notes
  2. Chrome console命令整理
  3. codeforces 709D D. Recover the String(构造)
  4. memcached for windows 修改端口和最大内存
  5. Jqgrid入门-别具特色的Pager Bar (四)
  6. Ajax概述
  7. ios9基础知识(UI)总结
  8. GitHub问题之恢复本地被删除的文件
  9. 给进程分配cpu核心
  10. mysql 开发基础系列19 触发器
  11. &lt;4&gt;Lua表
  12. ML(7)——支持向量机1(构建支持向量机)
  13. [转] 常用的CSS命名规则
  14. MyGeneration使用概述
  15. OneZero第二周第四次站立会议(2016.3.31)
  16. 【bzoj】3224: Tyvj 1728 普通平衡树
  17. BOM对象思维导图
  18. tensorflow初次接触记录,我用python写的tensorflow第一个模型
  19. 简单的文件上传html+ashx
  20. [.Net]System.OutOfMemoryException异常

热门文章

  1. 2-SAT&#183;hihoCoder音乐节
  2. elasticsearch 分析器阅读笔记(五)
  3. 数位dp无前导零
  4. Docker入门介绍
  5. arm32位固定指令中怎么容纳32位变量
  6. nyoj-20-吝啬的国度(深搜)
  7. C#替换字符串起始/结尾指定的字符串
  8. nyoj--973--天下第一(SPFA判断负环)
  9. V8 引擎是如何工作的?
  10. Windows下配置SVN服务器