最近在切二分匹配的题目,以前只学了点概念,趁现在有时间仔细得学下,下午和晚上花了几个小时学了KM算法,开始比较难理解,看了lrj的ppt后,有点明白了,现在整理下,以便记住。
概念:
lx(u),ly(v)分别表示左右两边的顶点顶标,即每一个点的值,这里有lx(u)+ly(v)>=w(u,v),选值时可令ly(v)=0,lx(u)为邻边权值最大的一个值。
相等子图:G的生成子图,仅包含满足lx(u)+ly(v)==w(u,v)的边(所以叫相等,我是这样理解的)
定理:如果相等子图有完美匹配,则该匹配是原图的最大匹配。(证明可以去搜刘汝佳的ppt)
思想:选定顶点的顶标,寻找相等子图的完美匹配,若找到,则终止算法,否则更新顶标,使更多的边满足lx(u)+ly(v)==w(u,v);循环直到算法终止。
一下是一个在网上找的代码以及个人的解析分享下(若有错误,谢谢指出):
#define INF 0x3f3f3f
cosnt int Max =1024;
int n; //顶点数
int w[Max][Max];
int lx[Max],ly[Max];
bool sx[Max],sy[Max]; //标记x,y
int mat[Max]; //匹配
bool path(int u) //寻找u的匹配,找增广路,基本和匈牙利的差不多
{
sx[u]=true;
for(int v=0;v<n;v++)
if(!sy[v]&&lx[u]+ly[v]==w[u][v]) //判断条件,是否满足lx[u]+ly[v]=w[u][v]
{
sy[v]=true;
if(mat[v]==-1||path(mat[v]))
{
mat[v]=u;
return true;
}
}
return false;
}
int KM(bool maxsum=true) //maxsum==true为最大权值,否则为最小
{
int i,j;
if(!maxsum) //由maxsum进行对边权修改
{
for(i=0;i<n;i++)
for(j=0;j<n;j++)
w[i][j]*=-1;
}
for(i=0;i<n;i++) //初始化lx,ly
{
lx[i]=-INF;
ly[i]=0;
for(j=0;j<n;j++)
lx[i]=max(w[i][j],lx[i]);
}
memset(mat,-1,sizeof(mat));
for(int u=0;u<n;u++)
while(1)
{
memset(sx,0,sizeof(sx));
memset(sy,0,sizeof(sy));
if(path(u)) break; //若找到匹配则跳出,判断下一个点
int dx=INF;
for(i=0;i<n;i++) //对访问过的点不满足lx[u]+ly[v]=w[u][v]的点对,找出最小差值dx
if(sx[i])
for(j=0;j<n;j++)
if(!sy[j])
dx=min(lx[i]+ly[j]-w[i][j],dx);
for(i=0;i<n;i++)
{
if(sx[i]) //对访问过的x更新,使满足差值dx的点更新后满足lx[i]+ly[i]==w[i][j],从而增加可行边
lx[i]-=dx;
if(sy[i]) //对访问过的y更新,使得已经满足条件的lx[i]+ly[i]==w[i][j]依然成立
ly[i]+=dx;
}
}
int sum=0;
for(i=0;i<n;i++)
sum+=w[mat[i]][i];
if(!maxsum) //若有必要,进行恢复
{
sum=-sum;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
w[i][j]*=-1;
}
return sum;
}