km算法主要还是通过和匈牙利算法一样的dfs不断求增广路。
大致过程可分为初始化可行性顶标、用匈牙利算法求完全匹配、求解失败则调整顶标值继续找、找到完全匹配后求和。
设顶点Xi的顶标为lx[i],顶点Yi的顶标为ly[i],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边(i,j),lx[i]+ly[j]>=w[i,j]始终成立。
KM算法的正确性基于以下定理:
若由二分图中所有满足lx[i]+ly[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。
初始时为了使lx[i]+ly[j]>=w[i,j]恒成立,令lx[i]为所有与顶点Xi关联的边的最大权,ly[j]=0。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边;
然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用vst搞一下),以进行后面的修改;
增广的结果有两种:若成功(找到了增广轨),则该点增广完成,进入下一个点的增广。若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d,则对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):
<1>i和j都在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
<2>i在增广轨中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
<3>j在增广轨中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
<4>i和j都不在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
这样,在进行了这一步修改操作后,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?显然,整个点标不能失去可行性,也就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的(lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。
修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;
下面分析整个算法的时间复杂度:每次修改后,图中至少会增加一条可行边,故最多增广M次、修改M次就可以找到仅由可行边组成的完全匹配(除非图中不存在完全匹配,这个可以通过预处理得到),故整个算法的时间复杂度为O(M * (N + 一次修改点标的时间))。而一次修改点标的时间取决于计算d值的时间,如果暴力枚举计算,这一步的时间为O(M),优化:可以对每个Y方点设立一个slk值,表示在DFS增广过程中,所有搜到的与该Y方点关联的边的(lx+ly-W)的最小值(这样的边的X方点必然在增广轨中)。每次DFS增广前,将所有Y方点的slk值设为+∞,若增广失败,则取所有不在增广轨中的Y方点的slk值的最小值为d值。这样一次修改点标的时间降为O(N),总时间复杂度降为O(NM)。
需要注意的一点是,在增广过程中需要记下每个X、Y方点是否被遍历到,即lx[i]、ly[j]。因此,在每次增广前(不是对每个X方点增广前)就要将所有lx和ly值清空。
hdu 2255
奔小康赚大钱
//完完全全的模板题
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <string>
#include <bitset>
#include <vector>
#include <iostream>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define mem(a,b) memset(a,b,sizeof(a))
#define For(a,l,r) for(int a=l;a<r;a++)
using namespace std;
typedef long long LL;
const LL mod = 9973;
const LL inf=0x3f3f3f3f;
const double pi=acos(-1);
const int N=320;//最大点数
const int M=1000020;// 最大边数
int nx,ny;
int g[N][N];
int linker[N],lx[N],ly[N];
int slack[N];
bool visx[N],visy[N];
bool dfs(int x)//寻找增广路
{
visx[x]=true;
for(int y=0;y<ny;y++)
{
if(visy[y])continue;
int tep=lx[x]+ly[y]-g[x][y];
if(tep==0)
{
visy[y]=true;
if(linker[y]==-1||dfs(linker[y]))
{
linker[y]=x;
return true;
}
}
else if(slack[y]>tep)
slack[y]=tep;
}
return false;
}
int KM()
{
memset(linker,-1,sizeof(linker));
memset(ly,0,sizeof(ly));
for(int i=0;i<nx;i++)
{
lx[i]=-inf;
for(int j=0;j<ny;j++)
if(g[i][j]>lx[i])
lx[i]=g[i][j];
}
for(int x=0;x<nx;x++)
{
for(int i=0;i<ny;i++)
slack[i]=inf;
while(true)
{
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(dfs(x))break;
int d=inf;//不断修改顶标直到找到增广路
for(int i=0;i<ny;i++)
if(!visy[i]&&d>slack[i])
d=slack[i];
for(int i=0;i<nx;i++)
if(visx[i])
lx[i]-=d;
for(int i=0;i<ny;i++)
{
if(visy[i])ly[i]+=d;
else slack[i]-=d;
}
}
}
int res=0;
for(int i=0;i<ny;i++)
if(linker[i]!=-1)
res+=g[linker[i]][i];
return res;
}
int main()
{
int n;
while(scanf("%d",&n)==1)
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&g[i][j]);
nx=ny=n;
printf("%d\n",KM());
}
return 0;
}
hdu 1533
Going Home
求最小权匹配 只需要把边权值取负,输出-KM()即可。
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <string>
#include <bitset>
#include <vector>
#include <iostream>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define mem(a,b) memset(a,b,sizeof(a))
#define F first
#define S second
using namespace std;
typedef long long LL;
const LL mod = 9973;
const LL inf=0x3f3f3f3f;
const double pi=acos(-1);
const int N=102;//最大点数
const int M=1000020;// 最大边数
char mapp[N][N];
pair<int,int>px[100],py[100];
int nx,ny;
int g[N][N];
int linker[N],lx[N],ly[N];
int slack[N];
bool visx[N],visy[N];
bool dfs(int x)
{
visx[x]=true;
for(int y=0;y<ny;y++)
{
if(visy[y])continue;
int tep=lx[x]+ly[y]-g[x][y];
if(tep==0)
{
visy[y]=true;
if(linker[y]==-1||dfs(linker[y]))
{
linker[y]=x;
return true;
}
}
else if(slack[y]>tep)
slack[y]=tep;
}
return false;
}
int KM()
{
memset(linker,-1,sizeof(linker));
memset(ly,0,sizeof(ly));
for(int i=0;i<nx;i++)
{
lx[i]=-inf;
for(int j=0;j<ny;j++)
if(g[i][j]>lx[i])
lx[i]=g[i][j];
}
for(int x=0;x<nx;x++)
{
for(int i=0;i<ny;i++)
slack[i]=inf;
while(true)
{
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(dfs(x))break;
int d=inf;
for(int i=0;i<ny;i++)
if(!visy[i]&&d>slack[i])
d=slack[i];
for(int i=0;i<nx;i++)
if(visx[i])
lx[i]-=d;
for(int i=0;i<ny;i++)
{
if(visy[i])ly[i]+=d;
else slack[i]-=d;
}
}
}
int res=0;
for(int i=0;i<ny;i++)
if(linker[i]!=-1)
res+=g[linker[i]][i];
return res;
}
int main()
{
#ifdef LOCALHEI
freopen("date.in","r",stdin);
freopen("date.out","w",stdout);
#endif
int n,m;
while(scanf("%d%d",&n,&m)==2)
{
if(n==0&&m==0)
break;
int tx=0,ty=0;
for(int i=0;i<n;i++)
scanf("%s",mapp[i]);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
if(mapp[i][j]=='H')
px[tx++]=make_pair(i,j);
else if(mapp[i][j]=='m')
py[ty++]=make_pair(i,j);
}
for(int i=0;i<tx;i++)
for(int j=0;j<ty;j++)
g[i][j]=-(abs(px[i].F-py[j].F)+abs(px[i].S-py[j].S));
nx=tx;
ny=ty;
printf("%d\n",-KM());
}
return 0;
}
hdu1853
Cyclic Tour
找若干个环使边权值之和最小,这个为什么能用二分最大权匹配呢?
可以想象一下若要把所有点都走一遍肯定有n个入度n个出度,而二分匹配刚好是使一个出度指向另一个没有入度的点,这恰好是最大匹配的思想。主意有重边。
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <string>
#include <bitset>
#include <vector>
#include <iostream>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define mem(a,b) memset(a,b,sizeof(a))
#define F first
#define S second
using namespace std;
typedef long long LL;
const LL mod = 9973;
const LL inf=0x3f3f3f3f;
const double pi=acos(-1);
const int N=102;//最大点数
const int M=1000020;// 最大边数
int nx,ny;
int g[N][N];
int linker[N],lx[N],ly[N];
int slack[N];
bool visx[N],visy[N];
bool dfs(int x)
{
visx[x]=true;
for(int y=0;y<ny;y++)
{
if(visy[y])continue;
int tep=lx[x]+ly[y]-g[x][y];
if(tep==0)
{
visy[y]=true;
if(linker[y]==-1||dfs(linker[y]))
{
linker[y]=x;
return true;
}
}
else if(slack[y]>tep)
slack[y]=tep;
}
return false;
}
int KM()
{
memset(linker,-1,sizeof(linker));
memset(ly,0,sizeof(ly));
for(int i=0;i<nx;i++)
{
lx[i]=-inf;
for(int j=0;j<ny;j++)
if(g[i][j]>lx[i])
lx[i]=g[i][j];
}
for(int x=0;x<nx;x++)
{
for(int i=0;i<ny;i++)
slack[i]=inf;
while(true)
{
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(dfs(x))break;
int d=inf;
for(int i=0;i<ny;i++)
if(!visy[i]&&d>slack[i])
d=slack[i];
for(int i=0;i<nx;i++)
if(visx[i])
lx[i]-=d;
for(int i=0;i<ny;i++)
{
if(visy[i])ly[i]+=d;
else slack[i]-=d;
}
}
}
int res=0;
for(int i=0;i<ny;i++)
{
if(linker[i]==-1||g[linker[i]][i]==-inf)
return 1;
if(linker[i]!=-1)
res+=g[linker[i]][i];
}
return res;
}
int main()
{
#ifdef LOCALHEI
freopen("date.in","r",stdin);
freopen("date.out","w",stdout);
#endif
int n,m;
while(scanf("%d%d",&n,&m)==2)
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
g[i][j]=-inf;
int u,v,c;
while(m--)
{
scanf("%d%d%d",&u,&v,&c);
u--;
v--;
if(-c>g[u][v])
g[u][v]=-c;
}
nx=n;
ny=n;
printf("%d\n",-KM());
}
return 0;
}