若由二分图中所有满足A[i]+B[i]=w[i][j]的边C(i,j)构成的子图(即相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配
设G=
通俗的理解,就是把V1中所有的点都匹配完
对于左边的点设为LX[maxn]数组,右边的点设为LY[maxn]数组,w[i][j]表示 v[i] 到 v[j] 的权值
相等子图为完备匹配中所有的匹配,即全部V1中的点和与V1中的点匹配的V2中的点,但是边只包含 LX[i]+LY[j]=W[i][j]的边
最优完备匹配就是在完备匹配的条件下求解权值最大或者最小,若由二分图中所有满足A[i]+B[i]=W[i][j]的边C(i,j)构成的相等子图有完备匹配,那么这个完备匹配就是二分图的最大权匹配
因为对于二分图的任意一个匹配,如果它包含相等子图,那么它的边权和等于所有顶点的顶标和;如果它有边不包含于相等子图,那么它的边权和小于所有顶点的顶标和,所以相等子图的完备匹配,一定是二分图的最大权匹配。
对V1中的一个顶点进行匹配的时候,所标记过的V1,V2中的点以及连线,形成一个树状的图
该算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题。
设顶点V1的顶标lx[i],V2顶点的顶标为LY[j],顶点V1的i与V2的j之间的边权为V(i,j)。在算法执行的过程中,对于任一条边C(i,j),LX[i]+LY[i]>=V[i,j]始终成立
(1)初始化时为了使 LX[i] + LY[j] >= V [i,j]恒成立,将V1的点的标号记为与其相连的最大边权值,V2的点标号记为0
(2)用匈牙利算法在相等子图寻找完备匹配
(3)若未找到完备匹配,则修改可行顶标的值,扩充相等子图
(4)重复(2)(3)直到找到相等子图的完备匹配为止
引用:
朴素的实现方法:时间复杂度为O(n4)——需要找O(n)次增广路, 每次增广最多需要修改O(n)次顶 标,每次修改顶标时由于要枚举边来求d值,复杂度为O(n2)。
实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数 slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与A [i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改 顶标后,要把所有的slack值都减去d。
引用:
如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止。
我们求当前相等子图的完备匹配失败了,是因为对于某个V1顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是V1顶点。现在我们把交错树中V1顶点的顶标全都减小某个值d,V2顶点的顶标全都增加同一个值d,那么我们会发现:
1)两端都在交错树中的边(i,j),lx[ i ]+ly[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
2)两端都不在交错树中的边(i,j),lx[ i ]和ly[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
3)V1端不在交错树中,V2端在交错树中的边(i,j),它的lx[ i ]+ly[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。
4)V1端在交错树中,V2端不在交错树中的边(i,j),它的lx[ i ]+ly[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
//HDU2255
#include
#include
#include
using namespace std;
#define maxn 310
#define INF 0x3f3f3f3f
#define clr(x) memset(x,0,sizeof(x))
int w[maxn][maxn];//w[i][j]表示i到j的权值
int lx[maxn],ly[maxn];//同时调节两个数组,使得权值和最大
int n;
//n1,n2为二分图的顶点集,其中x属于n1,y属于n2
//link记录n2中的点y在n1中所匹配的x点的编号
int link[maxn];
int slack[maxn];//松弛操作
int visx[maxn],visy[maxn];
bool dfs(int x)
{
visx[x]=1;//得到发生矛盾的居民集合
//对于这个居民,每个房子都试一下,找到就退出
for(int y=1;y<=n;y++){
if(visy[y]) continue;//不需要重复访问
int t=lx[x]+ly[y]-w[x][y];//这个条件下使用匈牙利算法
if(t==0)//标志这个房子可以给这个居民
{
visy[y]=1;
//这个房子没人住或者可以让住着个房子的人去找另外的房子住
if(link[y]==0||dfs(link[y]))
{
link[y]=x;
return 1;//可以让这位居民住进来
}
}
else if(slack[y]>t)//否则这个房子不能给这位居民
slack[y]=t;
}
return 0;
}
int KM()
{
clr(lx);
clr(ly);
clr(link);
//首先把每个居民出的钱最多的那个房子给他
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(lx[i]slack[k])
d=slack[k];//找到最小松弛量
for(int k=1;k<=n;k++)//松弛操作,使发生矛盾的居民有更多选择
{
if(visx[k]) lx[k]-=d;
//将矛盾居民的要求降低,使发生矛盾的居民有更多
if(visy[k]) ly[k]+=d;
//使发生矛盾的房子在下一个子图,保持矛盾
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans+=w[link[i]][i];
return ans;
}
int main()
{
while(~scanf("%d",&n)){
clr(w);//每个案例都重置为0
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&w[i][j]);//输入每条边的权值
printf("%d\n",KM());
}
return 0;
}