二分图的完美匹配
今天学了KM算法,用于二分图的完美匹配,以前就遇到过很多次,但是一直都没有花时间去学
学的比较挫,写的是n^4的算法
实际上有m*m*n的算法的
下边几道是完美匹配的题目
http://info.zjfc.edu.cn/acm/problemDetail.aspx?pid=1222 赤裸裸的完美匹配,图都建好了
http://acm.hdu.edu.cn/showproblem.php?pid=1533 这个建图很容易
http://acm.hdu.edu.cn/showproblem.php?pid=2282 这个需要建图
下边是http://acm.hdu.edu.cn/showproblem.php?pid=1533 的AC代码
////////////////////////////////////////////////////////////////////////
//
// 二分图的完美匹配,Kuhn-Munkras算法
#include < stdio.h >
#include < math.h >
#include < string >
#define MAXN 101
//////////////////////////////////////////////////////////////////////// //
#define inf 0x7FFFFFFF
int edge[MAXN][MAXN];
int match[MAXN];
bool hash[MAXN][MAXN];
bool xhash[MAXN],yhash[MAXN];
char map[MAXN][MAXN];
int min( int a, int b){ return a > b ? b:a;}
int max( int a, int b){ return a > b ? a:b;}
void Scanf( int n, int m, int & l);
bool dfs( int p, int n) // 找增广路径
{
int i;
for (i = 0 ;i < n;i ++ )
{
if ( ! yhash[i] && hash[p][i])
{
yhash[i] = true ;
int t = match[i];
if (t == - 1 || dfs(t,n))
{
match[i] = p;
return true ;
}
if (t != - 1 )
xhash[t] = true ;
}
}
return false ;
}
void show( int id, int n)
{
int i,j;
for (i = 0 ;i < n;i ++ )
{
for (j = 0 ;j < n;j ++ )
{
if (id)
printf( " %d " ,edge[i][j]);
else
printf( " %d " ,hash[i][j]);
}
puts( "" );
}
puts( "" );
}
void KM_Perfect_Match( int n)
{
int i,j;
int xl[MAXN],yl[MAXN];
for (i = 0 ;i < n;i ++ )
{
xl[i] = 0 ;
yl[i] = 0 ;
for (j = 0 ;j < n;j ++ )
xl[i] = max(xl[i],edge[i][j]);
}
bool perfect = false ;
while ( ! perfect)
{
for (i = 0 ;i < n;i ++ ) // 现阶段已经匹配的路
{
for (j = 0 ;j < n;j ++ )
{
if (xl[i] + yl[j] == edge[i][j])
hash[i][j] = true ;
else
hash[i][j] = false ;
}
}
// show(0,n);
int cnt = 0 ;
memset(match, - 1 , sizeof (match));
for (i = 0 ;i < n;i ++ ) // 当前的图是否能全部匹配
{
memset(xhash, false , sizeof (xhash));
memset(yhash, false , sizeof (yhash));
if (dfs(i,n))
cnt ++ ;
else
{
xhash[i] = true ;
break ;
}
}
if (cnt == n) // 没有增广路径
perfect = true ;
else
{
int ex = inf;
for (i = 0 ;i < n;i ++ )
{
for (j = 0 ;xhash[i] && j < n;j ++ )
{
if ( ! yhash[j])
ex = min(ex,xl[i] + yl[j] - edge[i][j]); // 找到一条没建的边的最小值
}
}
for (i = 0 ;i < n;i ++ ) // 根据这个边来进行松弛
{
if (xhash[i])
xl[i] -= ex;
if (yhash[i])
yl[i] += ex;
}
}
}
}
int main()
{
int n,m,l;
while (scanf( " %d%d " , & n, & m))
{
if (n == 0 && m == 0 )
break ;
Scanf(n,m,l);
KM_Perfect_Match(l);
int mindis = 0 ;
for ( int i = 0 ;i < l;i ++ )
mindis += edge[match[i]][i];
printf( " %d\n " , - mindis);
}
return 0 ;
}
// 读入建图
void Scanf( int n, int m, int & l)
{
int i,j,l1,l2;
struct Point{
int x,y;
}MAN[MAXN],HOUSE[MAXN];
l1 = l2 = 0 ;
for (i = 0 ;i < n;i ++ )
{
scanf( " %s " ,map[i]);
for (j = 0 ;j < m;j ++ )
{
if (map[i][j] == ' m ' )
{
MAN[l1].x = i;
MAN[l1].y = j;
l1 ++ ;
}
else if (map[i][j] == ' H ' )
{
HOUSE[l2].x = i;
HOUSE[l2].y = j;
l2 ++ ;
}
}
}
l = l1;
for (i = 0 ;i < l;i ++ )
for (j = 0 ;j < l;j ++ )
edge[i][j] = - abs(MAN[i].x - HOUSE[j].x) - abs(MAN[i].y - HOUSE[j].y);
}
// 二分图的完美匹配,Kuhn-Munkras算法
#include < stdio.h >
#include < math.h >
#include < string >
#define MAXN 101
//////////////////////////////////////////////////////////////////////// //
#define inf 0x7FFFFFFF
int edge[MAXN][MAXN];
int match[MAXN];
bool hash[MAXN][MAXN];
bool xhash[MAXN],yhash[MAXN];
char map[MAXN][MAXN];
int min( int a, int b){ return a > b ? b:a;}
int max( int a, int b){ return a > b ? a:b;}
void Scanf( int n, int m, int & l);
bool dfs( int p, int n) // 找增广路径
{
int i;
for (i = 0 ;i < n;i ++ )
{
if ( ! yhash[i] && hash[p][i])
{
yhash[i] = true ;
int t = match[i];
if (t == - 1 || dfs(t,n))
{
match[i] = p;
return true ;
}
if (t != - 1 )
xhash[t] = true ;
}
}
return false ;
}
void show( int id, int n)
{
int i,j;
for (i = 0 ;i < n;i ++ )
{
for (j = 0 ;j < n;j ++ )
{
if (id)
printf( " %d " ,edge[i][j]);
else
printf( " %d " ,hash[i][j]);
}
puts( "" );
}
puts( "" );
}
void KM_Perfect_Match( int n)
{
int i,j;
int xl[MAXN],yl[MAXN];
for (i = 0 ;i < n;i ++ )
{
xl[i] = 0 ;
yl[i] = 0 ;
for (j = 0 ;j < n;j ++ )
xl[i] = max(xl[i],edge[i][j]);
}
bool perfect = false ;
while ( ! perfect)
{
for (i = 0 ;i < n;i ++ ) // 现阶段已经匹配的路
{
for (j = 0 ;j < n;j ++ )
{
if (xl[i] + yl[j] == edge[i][j])
hash[i][j] = true ;
else
hash[i][j] = false ;
}
}
// show(0,n);
int cnt = 0 ;
memset(match, - 1 , sizeof (match));
for (i = 0 ;i < n;i ++ ) // 当前的图是否能全部匹配
{
memset(xhash, false , sizeof (xhash));
memset(yhash, false , sizeof (yhash));
if (dfs(i,n))
cnt ++ ;
else
{
xhash[i] = true ;
break ;
}
}
if (cnt == n) // 没有增广路径
perfect = true ;
else
{
int ex = inf;
for (i = 0 ;i < n;i ++ )
{
for (j = 0 ;xhash[i] && j < n;j ++ )
{
if ( ! yhash[j])
ex = min(ex,xl[i] + yl[j] - edge[i][j]); // 找到一条没建的边的最小值
}
}
for (i = 0 ;i < n;i ++ ) // 根据这个边来进行松弛
{
if (xhash[i])
xl[i] -= ex;
if (yhash[i])
yl[i] += ex;
}
}
}
}
int main()
{
int n,m,l;
while (scanf( " %d%d " , & n, & m))
{
if (n == 0 && m == 0 )
break ;
Scanf(n,m,l);
KM_Perfect_Match(l);
int mindis = 0 ;
for ( int i = 0 ;i < l;i ++ )
mindis += edge[match[i]][i];
printf( " %d\n " , - mindis);
}
return 0 ;
}
// 读入建图
void Scanf( int n, int m, int & l)
{
int i,j,l1,l2;
struct Point{
int x,y;
}MAN[MAXN],HOUSE[MAXN];
l1 = l2 = 0 ;
for (i = 0 ;i < n;i ++ )
{
scanf( " %s " ,map[i]);
for (j = 0 ;j < m;j ++ )
{
if (map[i][j] == ' m ' )
{
MAN[l1].x = i;
MAN[l1].y = j;
l1 ++ ;
}
else if (map[i][j] == ' H ' )
{
HOUSE[l2].x = i;
HOUSE[l2].y = j;
l2 ++ ;
}
}
}
l = l1;
for (i = 0 ;i < l;i ++ )
for (j = 0 ;j < l;j ++ )
edge[i][j] = - abs(MAN[i].x - HOUSE[j].x) - abs(MAN[i].y - HOUSE[j].y);
}