http://acm.hdu.edu.cn/showproblem.php?pid=2426
题目大意:住房分配。现有N个学生,M间房。每个学生对一部分房有评估值,用一个整数表示,正数表示喜欢,0表示中立,负数表示不喜欢,没有评估的不能住。校长要给这些学生分房,要求每个学生都不住他们不喜欢住的房。如果可能,输出最大的满意度,否则,输出-1。
算法分析:最优二分匹配(或称最佳匹配)。用KM算法解决。
这是我第一次写KM,写的还是很烂。百度百科有KM算法介绍(http://baike.baidu.com/view/739278.htm?fr=ala0_1_1),写的还可以。用百度模板写了一下。跑了1671ms。
#include < stdio.h >
#include < string .h >
#define NN 502
#define INF 0xfffffff
int visitx[NN], visity[NN], mat[NN][NN];
int x[NN], y[NN], link[NN];
int N, M, slack;
int find( int t)
{
int i, tmp;
visitx[t] = 1 ;
for (i = 0 ; i < M; i ++ ){
if ( ! visity[i]){
tmp = x[t] + y[i] - mat[t][i];
if (tmp == 0 )
{
visity[i] = 1 ;
if (link[i] == - 1 || find(link[i])){
link[i] = t;
return 1 ;
}
} else if (tmp < slack)
slack = tmp;
}
}
return 0 ;
}
void KM()
{
int i, j;
for (i = 0 ; i < N; i ++ ){
x[i] = 0 ;
for (j = 0 ; j < M; j ++ ){
if (mat[i][j] > x[i])
x[i] = mat[i][j];
}
}
for (j = 0 ; j < M; j ++ ){
y[j] = 0 ;
}
memset(link, - 1 , sizeof (link));
for (i = 0 ; i < N; i ++ ){
while ( 1 )
{
memset(visitx, 0 , sizeof (visitx));
memset(visity, 0 , sizeof (visity));
slack = INF;
if (find(i)) break ;
for (j = 0 ; j < N; j ++ ){
if (visitx[j]) x[j] -= slack;
}
for (j = 0 ; j < M; j ++ ){
if (visity[j]) y[j] += slack;
}
}
}
}
int main()
{
int i, j, a, b, c, E, ans, t, cnt;
int icase = 1 ;
while (scanf( " %d%d%d " , & N, & M, & E) != EOF){
for (i = 0 ; i < N; i ++ )
for (j = 0 ; j < M; j ++ )
mat[i][j] = - INF;
if (E == 0 ){
printf( " Case %d: " , icase ++ );
puts( " -1 " );
continue ;
}
while (E -- ){
scanf( " %d%d%d " , & a, & b, & c);
if (c < 0 )
continue ;
mat[a][b] = c;
}
KM();
printf( " Case %d: " , icase ++ );
ans = 0 ;
cnt = 0 ;
for (i = 0 ; i < M; i ++ ){
t = link[i];
if (t >= 0 && mat[t][i] != - INF){
cnt ++ ;
ans += mat[t][i];
}
}
if (cnt < N)
ans = - 1 ;
printf( " %d\n " , ans);
}
return 0 ;
}
这个模板和文字说的好像不太一样,不知是不是O(n3)的。自己又收了了个,和文字介绍的一个意思,每一个y节点加了一个松弛量,用数组slack[]保存,比较快390ms,但我个人并没有感觉到这两个写法的区别,待考察。
#include < stdio.h >
#include < string .h >
#define NN 502
#define INF 0xfffffff
int visitx[NN], visity[NN], mat[NN][NN];
int x[NN], y[NN], link[NN];
int N, M, slack[NN];
int find( int t)
{
int i, tmp;
visitx[t] = 1 ;
for (i = 0 ; i < M; i ++ ){
if ( ! visity[i]){
tmp = x[t] + y[i] - mat[t][i];
if (tmp == 0 )
{
visity[i] = 1 ;
if (link[i] == - 1 || find(link[i])){
link[i] = t;
return 1 ;
}
} else if (tmp < slack[i])
slack[i] = tmp;
}
}
return 0 ;
}
void KM()
{
int i, j, min;
for (i = 0 ; i < N; i ++ ){
x[i] = 0 ;
for (j = 0 ; j < M; j ++ ){
if (mat[i][j] > x[i])
x[i] = mat[i][j];
}
}
for (j = 0 ; j < M; j ++ ){
y[j] = 0 ;
}
memset(link, - 1 , sizeof (link));
for (i = 0 ; i < N; i ++ ){
for (j = 0 ; j < M; j ++ )
slack[j] = INF;
while ( 1 )
{
memset(visitx, 0 , sizeof (visitx));
memset(visity, 0 , sizeof (visity));
min = INF;
if (find(i)) break ;
for (j = 0 ; j < M; j ++ ){
if ( ! visity[j] && slack[j] < min)
min = slack[j];
}
for (j = 0 ; j < N; j ++ ){
if (visitx[j]) x[j] -= min;
}
for (j = 0 ; j < M; j ++ ){
if (visity[j]) y[j] += min;
else slack[j] -= min;
}
}
}
}
int main()
{
int i, j, a, b, c, E, ans, t, cnt;
int icase = 1 ;
while (scanf( " %d%d%d " , & N, & M, & E) != EOF){
for (i = 0 ; i < N; i ++ )
for (j = 0 ; j < M; j ++ )
mat[i][j] = - INF;
if (E == 0 ){
printf( " Case %d: " , icase ++ );
puts( " -1 " );
continue ;
}
while (E -- ){
scanf( " %d%d%d " , & a, & b, & c);
if (c < 0 )
continue ;
mat[a][b] = c;
}
KM();
printf( " Case %d: " , icase ++ );
ans = 0 ;
cnt = 0 ;
for (i = 0 ; i < M; i ++ ){
t = link[i];
if (t >= 0 && mat[t][i] != - INF){
cnt ++ ;
ans += mat[t][i];
}
}
if (cnt < N)
ans = - 1 ;
printf( " %d\n " , ans);
}
return 0 ;
}
这题还有个小陷阱,如果估计值小于0就不用考虑了。
注意这组测试数据
2 2 4
0 0 1
0 1 4
1 0 -1
1 1 1
正确解为2