POJ2195 最大权匹配KM算法

题意:有N个人在一张地图上要回家,问每个人都到家所需要的总的最短距离。

 

可以用最小费用最大流来做,同样建立一个超级源点一个超级汇点。

 

但是该图构图后是一个标准的二分图,所以用KM算法来求二分图的最小权匹配。

 

KM算法:

目的:求出边权和最大的完全匹配,

给出标号lx[i],ly[j]   ,对KM算法的任何时刻有lx[i]+ly[j]>=w[i][j],

可以通过找到所有匹配边w[i][j]使lx[i]+ly[j]==w[i][j],

总之一句话:相等子图的完备匹配一定是该二分图的最大权匹配。

 

初始化lx[i]=Max(w[i][j])  ly[j]=0;

依次对每个顶点i,DFS找有lx[i]+ly[j]==w[i][j]的边,如果存在则进行类似匈牙利算法的匹配。

如果不存在,我们需要减小一些点标的值使图中出现满足lx[i]+ly[j]==w[i][j]的边从而增加匹配,当然这个减小的值应该最小化

 

我们求当前相等子图的完备匹配失败了,是因为对于某个i顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是i顶点。现在我们把交错树中i顶点的顶标全都减小某个值d,j顶点的顶标全都增加同一个值d,那么我们会发现:
1)两端都在交错树中的边(i,j),lx[i]+ly[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
2)两端都不在交错树中的边(i,j),lx[i]和ly[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
3)X端不在交错树中,Y端在交错树中的边(i,j),它的lx[i]+ly[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。
4)X端在交错树中,Y端不在交错树中的边(i,j),它的lx[i]+ly[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
现在的问题就是求d值了。为了使lx[i]+ly[j]>=w[i,j]始终成立,且至少有一条边进入相等子图,d应该等于:
Min{lx[i]+ly[j]-w[i,j] | Xi在交错树中,Yi不在交错树中}。
 
更新完lx,ly后,继续对该顶点找增广路径。
 
优化:
由于求d值需要枚举每条边:for(i=1;i<=n;i++){
                                                 for(j=1;j<=n;j++){
                                                           if(vis[i]&&!vis[j]) d=min(lx[i]+lx[j]-w[i][j],d);
                                                 }
                                              }
复杂度为O(n^2)
应该我们给Yi定义一个松弛量Slack[j]:表示对于所有i在相等子图中的边w[i][j],d的最小值,对于Slack的更新在寻找增广路中进行
 
注意!!!修改顶点表号时,要把所有的Slack值也都减小d!!!!!
 
回到该题
题目要求的最小的权匹配,则所有边取相反数后求最大匹配,得出的结果再求相反数就会最小匹配了
 
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define INF 99999999
#define Min(a,b) (a<b?a:b)
#define Max(a,b) (a>b?a:b)

//求最小权匹配只需要把最大权匹配的权取相反数输出结果时再取相反数就可以
 
int N,M;
int Home[101][2],X;
int Man[101][2],Y;
int Map[101][101];
int VisX[101],VisY[101],LX[101],LY[101],Slack[101];
int Ma[101];

int Find(int i){
	int j,d;
	VisX[i]=1;
	for(j=0;j<Y;j++){
		if(VisY[j])
			continue;
		d=LX[i]+LY[j]-Map[i][j];
		if(d==0){
			VisY[j]=1;
			if(Ma[j]==-1||Find(Ma[j])){
				Ma[j]=i;
				return 1;
			}
		}
		else
			Slack[j]=Min(Slack[j],d);
	}
	return 0;
}

int KM(){
	int k,i,j,d,Ans;
	memset(Ma,-1,sizeof(Ma));
	memset(LY,0,sizeof(LY));
	for(i=0;i<X;i++){
		LX[i]=(-1)*INF;
		for(j=0;j<Y;j++)
			LX[i]=Max(LX[i],Map[i][j]);
	}
	for(k=0;k<X;k++){
		for(j=0;j<Y;j++)
			Slack[j]=INF;
		while(1){
			memset(VisX,0,sizeof(VisX));
			memset(VisY,0,sizeof(VisY));
			if(Find(k))
				break;
			d=INF;
			for(j=0;j<Y;j++)
				if(!VisY[j])
					d=Min(Slack[j],d);
			for(i=0;i<X;i++)
				if(VisX[i])
					LX[i]-=d;
			for(j=0;j<Y;j++)
				if(VisY[j])
					LY[j]+=d;
				else
					Slack[j]-=d;
		}
	}
	Ans=0;
	for(j=0;j<Y;j++)
		Ans+=Map[Ma[j]][j];
	return Ans;	
}



int main(){
	int i,j;
	char c;
	while(~scanf("%d %d",&N,&M)){
		if(N==0&&M==0) break;
		X=Y=0;
		for(i=0;i<N;i++){
			for(j=0;j<M;j++){
				do{
					scanf("%c",&c);
				}while(c=='\n');
				if(c=='H'){
					Home[X][0]=i;
					Home[X++][1]=j;
				}
				else if(c=='m'){
					Man[Y][0]=i;
					Man[Y++][1]=j;
				}
			}
		}
		for(i=0;i<Y;i++){
			for(j=0;j<X;j++){
				Map[i][j]=(-1)*(abs(Man[i][0]-Home[j][0])+abs(Man[i][1]-Home[j][1]));
			}
		}
		printf("%d\n",(-1)*KM());
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值