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