求最大权二分匹配的KM算法

求最大权二分匹配的KM算法

最大权二分匹配问题就是给二分图的每条边一个权值,选择若干不相交的边,得到的总权值最大。解决这个问题可以用KM算法。理解KM算法需要首先理解“可行顶标”的概念。可行顶标是指关于二分图两边的每个点的一个值lx[i]或ly[j],保证对于每条边w[i][j]都有lx[i]+ly[j]-w[i][j]>=0。如果所有满足lx[i]+ly[j]==w[i][j]的边组成的导出子图中存在一个完美匹配,那么这个完美匹配肯定就是原图中的最大权匹配。理由很简单:这个匹配的权值之和恰等于所有顶标的和,由于上面的那个不等式,另外的任何匹配方案的权值和都不会大于所有顶标的和。

但问题是,对于当前的顶标的导出子图并不一定存在完美匹配。这时,可以用某种方法对顶标进行调整。调整的方法是:根据最后一次不成功的寻找交错路的DFS,取所有i被访问到而j没被访问到的边(i,j)的lx[i]+ly[j]-w[i][j]的最小值d。将交错树中的所有左端点的顶标减小d,右端点的顶标增加d。经过这样的调整以后:原本在导出子图里面的边,两边的顶标都变了,不等式的等号仍然成立,仍然在导出子图里面;原本不在导出子图里面的边,它的左端点的顶标减小了,右端点的顶标没有变,而且由于d的定义,不等式仍然成立,所以他就可能进入了导出子图里。

初始时随便指定一个可行顶标,比如说lx[i]=max{w[i][j]|j是右边的点},ly[i]=0。然后对每个顶点进行类似Hungary算法的find过程,如果某次find没有成功,则按照这次find访问到的点对可行顶标进行上述调整。这样就可以逐步找到完美匹配了。

值得注意的一点是,按照上述d的定义去求d的话需要O(N^2)的时间,因为d需要被求O(N^2)次,这就成了算法的瓶颈。可以这样优化:设slack[j]表示右边的点j的所有不在导出子图的边对应的lx[i]+ly[j]-w[i][j]的最小值,在find过程中,若某条边不在导出子图中就用它对相应的slack值进行更新。然后求d只要用O(N)的时间找到slack中的最小值就可以了。

如果是求最小权匹配,只需要把那个不等式反一下就行了。算法需要作出的改变是:lx的初值为所有临界边中的最小值,find中t反号。

示例程序(Ural 1076):

#include <cstdio>
#include <cstring>
using namespace std;

const int maxn=160,OO=2147483647;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int linky[maxn];
int visx[maxn],visy[maxn];
int N;
int slack[maxn];

void input(){
	scanf("%d",&N);
	for(int i=0;i<N;++i)
		for(int j=0;j<N;++j)
			scanf("%d",&w[i][j]);
}
bool find(int x){
	visx[x]=true;
	for(int y=0;y<N;++y){
		if(visy[y])continue;
		int t=lx[x]+ly[y]-w[x][y];
		if(t==0){
			visy[y]=true;
			if(linky[y]==-1||find(linky[y])){
				linky[y]=x;
				return true;
			}
		}
		else{
			if(slack[y]>t)
				slack[y]=t;
		}
	}
	return false;
}
void KM(){
	memset(linky,-1,sizeof(linky));
	memset(lx,0,sizeof(lx));
	memset(ly,0,sizeof(ly));
	for(int i=0;i<N;++i)
		for(int j=0;j<N;++j)
			if(w[i][j]>lx[i])
				lx[i]=w[i][j];
	for(int x=0;x<N;++x){
		for(int i=0;i<N;++i)
			slack[i]=OO;
		for(;;){
			memset(visx,0,sizeof(visx));
			memset(visy,0,sizeof(visy));
			if(find(x))break;
			int d=OO;
			for(int i=0;i<N;++i){
				if(!visy[i])
					if(d>slack[i])
						d=slack[i];
			}
			for(int i=0;i<N;++i){
				if(visx[i])
					lx[i]-=d;
			}
			for(int i=0;i<N;++i){
				if(visy[i])
					ly[i]+=d;
				else
					slack[i]-=d;
			}
		}
	}
}
void output(){
	int res=0;
	for(int j=0;j<N;++j){
		for(int i=0;i<N;++i)
			res+=w[i][j];
		res-=w[linky[j]][j];
	}
	printf("%d\n",res);
}
int main(){
	input();
	KM();
	output();
}



你可能感兴趣的:(求最大权二分匹配的KM算法)