二分图匹配KM算法 POJ2195

POJ2195 给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离),一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。题意来源:http://blog.csdn.net/lyy289065406/article/details/6732762

        最小费用取相反数 转化为带权最大匹配:

//最小费用转化为最大匹配
//KM算法
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define Maxn 120
struct Node{
	int x,y;
}g[Maxn],h[Maxn];
int map[Maxn][Maxn],match[Maxn],slack[Maxn],lx[Maxn],ly[Maxn];
bool visx[Maxn],visy[Maxn];
char st[Maxn][Maxn];
int N,M,n,m;
int abs(int x){
	if (x<0) x=-x;return x;
}
int Dis(Node a,Node b){
	return abs(a.x-b.x)+abs(a.y-b.y);
}
void init(){
	memset(map,0,sizeof(map));
	n=m=0;
	for (int i=1;i<=N;i++){
		scanf("%s",st[i]+1);st[i][0]='~';
		for (int j=1;j<=M;j++){
			if (st[i][j]=='m'){
				n++;
				g[n].x=i;g[n].y=j;
			}
			if (st[i][j]=='H'){
				m++;
				h[m].x=i;h[m].y=j;
			}
		}
	}
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			map[i][j]=-Dis(g[i],h[j]);
		}
	}
}
bool dfs(int i){
	visx[i]=true;
	for (int j=1;j<=m;j++){
		int tmp=lx[i]+ly[j]-map[i][j];
		if (visy[j]) continue;
		if (tmp==0){
			visy[j]=true;
			if (match[j]==-1|| dfs(match[j])){
				match[j]=i;
				return true;
			}
		}
		else if (tmp<slack[j]){
			slack[j]=tmp;
		}
	}
	return false;
}
int KM(){
	memset(match,-1,sizeof(match));
	//memset(lx,0,sizeof(lx));
	memset(ly,0,sizeof(ly));
	for (int i=1;i<=n;i++){
		lx[i]=-INF;
		for (int j=1;j<=m;j++){
			if (lx[i]<map[i][j]) lx[i]=map[i][j];
		}
	}
	for (int k=1;k<=n;k++){
		for (int i=1;i<=n;i++) slack[i]=INF;
		while(1){
			memset(visx,false,sizeof(visx));
			memset(visy,false,sizeof(visy));
			if (dfs(k)) break;
			int dx=INF;
			for (int i=1;i<=m;i++){
				if (!visy[i]&&dx>slack[i]) dx=slack[i];
			}
			for (int i=1;i<=n;i++){
				if (visx[i]) lx[i]-=dx;
			}
			for (int i=1;i<=m;i++){
				if (visy[i]) ly[i]+=dx;
				else slack[i]-=dx;
			}
		}
	}

	int ret=0;
	for (int i=1;i<=m;i++)
		ret+=map[match[i]][i];
	return -ret;
}

int main(){
	while (~scanf("%d%d",&N,&M),N||M){
		init();
		int ans=KM();
		printf("%d\n",ans);
	}
	return 0;
}



由于slack数组只要求一个最小值,我们可以省略一个数组来这么写:


#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define Maxn 120
struct Node{
	int x,y;
}g[Maxn],h[Maxn];
int map[Maxn][Maxn],match[Maxn],lx[Maxn],ly[Maxn];
bool visx[Maxn],visy[Maxn];
char st[Maxn][Maxn];
int N,M,n,m,slack;
int abs(int x){
	if (x<0) x=-x;return x;
}
int Dis(Node a,Node b){
	return abs(a.x-b.x)+abs(a.y-b.y);
}
void init(){
	memset(map,0,sizeof(map));
	n=m=0;
	for (int i=1;i<=N;i++){
		scanf("%s",st[i]+1);st[i][0]='~';
		for (int j=1;j<=M;j++){
			if (st[i][j]=='m'){
				n++;
				g[n].x=i;g[n].y=j;
			}
			if (st[i][j]=='H'){
				m++;
				h[m].x=i;h[m].y=j;
			}
		}
	}
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			map[i][j]=-Dis(g[i],h[j]);
		}
	}
}
bool dfs(int i){
	visx[i]=true;
	for (int j=1;j<=m;j++){
		int tmp=lx[i]+ly[j]-map[i][j];
		if (visy[j]) continue;
		if (tmp==0){
			visy[j]=true;
			if (match[j]==-1|| dfs(match[j])){
				match[j]=i;
				return true;
			}
		}
		else if (tmp<slack){
			slack=tmp;
		}
	}
	return false;
}
int KM(){
	memset(match,-1,sizeof(match));
	//memset(lx,0,sizeof(lx));
	memset(ly,0,sizeof(ly));
	for (int i=1;i<=n;i++){
		lx[i]=-INF;
		for (int j=1;j<=m;j++){
			if (lx[i]<map[i][j]) lx[i]=map[i][j];
		}
	}
	for (int k=1;k<=n;k++){
		while(1){
			memset(visx,false,sizeof(visx));
			memset(visy,false,sizeof(visy));
			slack=INF;
			if (dfs(k)) break;
			int dx=slack;
			for (int i=1;i<=n;i++){
				if (visx[i]) lx[i]-=dx;
				if (visy[i]) ly[i]+=dx;
			}
		}
	}

	int ret=0;
	for (int i=1;i<=m;i++)
		ret+=map[match[i]][i];
	return -ret;
}

int main(){
	while (~scanf("%d%d",&N,&M),N||M){
		init();
		int ans=KM();
		printf("%d\n",ans);
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值