二分图最大权完美匹配KM算法是在一个二分图里,求一个最大权匹配,但是要求这个匹配必须是完美匹配。如果匹配不一定是完美匹配,那么似乎只能将其转化为最小费用最大流来做了。我们可以使用KM算法对任意带权(无论正负权)二分图求最大/最小权完美匹配,它的算法复杂度是O(n3),但是如果写得不好会变成O(n4)。
KM算法是通过给每个顶点一个标号(我们有时称之为顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。我们令二分图中X部的节点的顶标为Ai,Y部的节点的顶标为Bi。X部与Y部节点之间的权值为Wi,j,那么,在算法进行的过程中,我们必须始终保持成立。因为KM算法的正确性基于以下定理:
若由二分图中所有满足Ai+Bi=Wi,j的边 (i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
初始时为了使成立,令Ai为所有与Xi顶点关联的边的权值的最大值,令Bi=0。如果当前的相等子图没有完备匹配,就需要修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止(这样就可以求出最大权匹配)。
如果当前相等子图找不到完备匹配,那么是因为对于某个X顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,现在我们把交错树中X部的顶点的顶标全都减小某个值d,Y部的顶点的顶标全都增加d,就可以使得至少有一条新边进入相等子图。这里d=min{Ai+Bi-Wi,j},而且Xi在交错树中,Yi不在。
但是如果仅仅是这样,用朴素的方法实现,算法复杂度是O(n4)。我们给每个Y部的顶点一个“松弛量”slack,每次开始找增广路时松弛量初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slackj变成原值与Ai+Bi-Wi,j的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改顶标后,要把所有的slackj值都减去d。经过这样一个优化实现后,算法复杂度就变成了O(n3)。
模板 "
#include
#include
#define N 1001
#define M 1<<30
int n;
int m[N][N]={0};
int match[N]={0};
int lx[N]={0},ly[N]={0};
bool x[N]={0},y[N]={0};
int Min(int a,int b) {return a
void init() //初始化
{
int i,j;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(m[i][j]>lx[i]) lx[i]=m[i][j]; //lx[i]赋边的值,ly[i]=0;
memset(match,-1,sizeof(match));
}
bool Find(int a) //用匈牙利找最大匹配
{
int i,d;
x[a]=true; //加入x子图.
for(i=1;i<=n;i++)
{
if(!y[i]&&m[a][i]==lx[a]+ly[i]) //y必须没被访问
{
y[i]=true;
if(match[i]==-1||Find(match[i]))
{
match[i]=a;
return true;
}
}
}
return false;
}
void KM()
{
int i,j,k,d;
int cost=0;
init();
for(i=1;i<=n;i++)
{
while(1)
{
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
if(Find(i)) break; //若找到最大匹配,结束.每一找到,则修改顶标
d=M; //初始修改量,尽量大.
for(j=1;j<=n;j++)
if(x[j]) //j点在x集合
for(k=1;k<=n;k++)
if(!y[k]) //k点还没加入进来
d=Min(d,lx[j]+ly[k]-m[j][k]); // 找最小的d
if(d==M) break; //没找到
for(j=1;j<=n;j++) //修改lx,ly
{
if(x[j]) lx[j]-=d;
if(y[j]) ly[j]+=d;
}
}
}
for(i=1;i<=n;i++) //最大的权值.
{
cost+=m[match[i]][i];
}
printf("\n%d\n",cost);
}
int main()
{
int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&m[i][j]);
KM();
scanf("%d",&i);
}