Description
On a grid map there are n little men and n houses. In each unit time, every little
man can move one unit step, either horizontally, or vertically, to an adjacent point. For each
little man, you need to pay a $1 travel fee for every step he moves, until he enters a house.
The task is complicated with the restriction that each house can accommodate only one little man.
Your task is to compute the minimum amount of money you need to pay in order to send these
n little men into those n different houses. The input is a map of the scenario, a '.' means an
empty space, an 'H' represents a house on that point, and am 'm' indicates there is a little
man on that point.
You can think of each point on the grid map as a quite large square, so it can hold n little
men at the same time; also, it is okay if a little man steps on a grid with a house without
entering that house.
Input
There are one or more test cases in the input. Each case starts with a line giving two integers
N and M, where N is the number of rows of the map, and M is the number of columns. The rest of
the input will be N lines describing the map. You may assume both N and M are between 2 and 100,
inclusive. There will be the same number of 'H's and 'm's on the map; and there will be at most
100 houses. Input will terminate with 0 0 for N and M.
Output
For each test case, output one line with the single integer, which is the minimum amount, in
dollars, you need to pay.
Sample Input
2 2
.m
H.
5 5
HH..m
.....
.....
.....
mm..H
7 8
...H....
...H....
...H....
mmmHmmmm
...H....
...H....
...H....
0 0
Sample Output
2
10
28
這道題可以用最小費用流算法來解決。
每次從源點出發,用所有還有殘量的邊按費用找到匯點的最短路,最後將沿途總費用與沿途最小流量的積累加即可。
Accode:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <bitset>
using std::bitset;
const char fi[] = "poj2195.in";
const char fo[] = "poj2195.out";
const int maxN = 2010;
const int SIZE = 1050000;
const int MOD = 1048575;
const int MAX = 0x3f3f3f3f;
const int MIN = ~MAX;
int dist[maxN]; //臨時最短距離。
int w[maxN][maxN]; //費用。
int F[maxN][maxN]; //殘量。
int x[maxN], y[maxN]; //該點的橫縱座標。
int q[SIZE]; //隊列。
int pre[maxN]; //記錄前驅結點。
bitset <maxN> man; //一個標記,表明該點是人還是房子。
bitset <maxN> marked; //是否在隊列中。
int N, M, n, f, r, ans;
void init_file()
{
freopen(fi, "r", stdin);
freopen(fo, "w", stdout);
}
inline int dis(int i, int j)
{
return abs(x[i] - x[j]) + abs(y[i] - y[j]);
} //計算i和j的距離(橫座標之差的絕對值
//加上縱座標之差的絕對值。)
inline void insert
(int u, int v, int d, int f1, int f2)
{
w[u][v] = d;
w[v][u] = -d;
//反向變費用設為負數,即為降少費用。
F[u][v] = f1;
F[v][u] = f2;
} //加邊。
void readdata()
{
scanf("%d%d", &N, &M);
if (!N || !M) exit(0);
memset(w, 0xc0, sizeof(w));
memset(F, 0, sizeof(F));
n = 1;
for (int i = 1; i < N + 1; ++i)
{
getchar();
for (int j = 1; j < M + 1; ++j)
switch (getchar())
{
case 'H':
++n;
x[n] = i;
y[n] = j;
man.reset(n);
break;
case 'm':
++n;
x[n] = i;
y[n] = j;
man.set(n);
break;
}
}
++n;
for (int i = 2; i < n; ++i)
if (man.test(i))
insert(1, i, 0, 1, 0);
else insert(i, n, 0, 1, 0);
for (int i = 2; i < n; ++i)
for (int j = 2; j < n; ++j)
if (man.test(i) && !man.test(j))
insert(i, j, dis(i, j), 1, 0);
}
inline void Enq(int t)
{
q[(++r) &= MOD] = t;
marked.set(t);
} //入隊。
inline int Deq()
{
(++f) &= MOD;
marked.reset(q[f]);
return q[f];
} //出隊。
bool Spfa()
{
f = r = 0;
memset(pre, 0xff, sizeof(pre));
memset(dist, 0x3f, sizeof(dist));
Enq(1);
dist[1] = 0;
while (f != r)
{
int u = Deq();
for (int v = 1; v < n + 1; ++v)
if (w[u][v] > MIN && F[u][v] > 0)
//該邊有殘量才能增廣。
{
if (dist[u] + w[u][v] < dist[v])
{
dist[v] = dist[u] + w[u][v];
pre[v] = u;
if (!marked.test(v)) Enq(v);
}
}
}
if (dist[n] < MAX) return true;
return false;
}
void work()
{
ans = 0;
while (Spfa())
{
int Min = MAX;
for (int i = n; pre[i] > -1; i = pre[i])
Min = std::min(Min, F[pre[i]][i]);
//找最小流量。
ans += Min * dist[n];
for (int i = n; pre[i] > -1; i = pre[i])
{
F[pre[i]][i] -= Min;
F[i][pre[i]] += Min;
}
//修改殘量網絡。
}
printf("%d\n", ans);
}
int main()
{
init_file();
while (1)
{
readdata();
work();
}
exit(0);
}
還可以用最佳匹配做,代碼略。