洛谷 P3356 火星探险问题 题解
前置知识:费用流
题目含义
有若干个机器人,在一个方格阵上向下或向右行走,从左上角走到右下角。机器人不能碰到障碍,能收集石块,收集后石块变为空地。问最多能收集多少个石块。
做法
我们用 n n n 表示方格阵的行数, m m m 表示列数, k k k 表示机器人的个数, ( i , j ) (i, j) (i,j) 表示第 i i i 行第 j j j 列。
建图方式
本题使用最大流拆点,把一个点拆成入点和出点。
-
从原点向 ( 1 , 1 ) (1, 1) (1,1) 的入点连一条容量为 k k k,费用为 0 0 0 的边。
-
从 ( n , m ) (n, m) (n,m) 的出点向汇点连一条容量为 k k k,费用为 0 0 0 的边。
-
对于每个 ( i , j ) ( 1 ≤ i ≤ n , 1 ≤ j ≤ m , i (i, j)(1 \le i \le n,1 \le j \le m,i (i,j)(1≤i≤n,1≤j≤m,i 不是障碍 ) ) ),如果 i < n i < n i<n 且 ( i + 1 , j ) (i + 1, j) (i+1,j) 不是障碍,则从 ( i , j ) (i, j) (i,j) 的出点向 ( i + 1 , j ) (i + 1, j) (i+1,j) 的入点连一条容量为 i n f inf inf,费用为 0 0 0 的边。如果 j < m j < m j<m,则从 ( i , j ) (i, j) (i,j) 的出点向 ( i , j + 1 ) (i, j + 1) (i,j+1) 连一条容量为 I N F INF INF,费用为 0 0 0 的边。
-
对于每个 ( i , j ) ( 1 ≤ i ≤ n , 1 ≤ j ≤ m , i (i, j)(1 \le i \le n,1 \le j \le m,i (i,j)(1≤i≤n,1≤j≤m,i 是空地 ) ) ),从 ( i , j ) (i, j) (i,j) 的入点向 ( i , j ) (i, j) (i,j) 的出点连一条容量为 i n f inf inf,费用为 0 0 0 的边。
-
对于每个 ( i , j ) ( 1 ≤ i ≤ n , 1 ≤ j ≤ m , i (i, j)(1 \le i \le n,1 \le j \le m,i (i,j)(1≤i≤n,1≤j≤m,i 是石块 ) ) ),从 ( i , j ) (i, j) (i,j) 的入点向 ( i , j ) (i, j) (i,j) 的出点连一条容量为 i n f inf inf,费用为 0 0 0 的边和一条容量为 1 1 1,费用为 1 1 1 的边。
最终求最大费用最大流就是答案。
为什么这样建图是对的
这里做一个简单的解释。
1,2容量是 k k k 表示有 k k k 个机器人,3容量是 i n f inf inf 表示经过次数没有限制,4容量是 i n f inf inf 表示空地没有经过次数限制,5容量是 i n f inf inf 和 1 1 1 表示只有一次采集了石块,其它都直接从上面经过。
1, 2费用是 0 0 0 表示机器人出发没有费用,3费用是 0 0 0 表示机器人行走没有费用,4费用是 0 0 0 表示经过空地没有费用,5费用是 0 0 0 和 1 1 1 表示只有第一次收集石块有 1 1 1 的费用,其余从经过都没有费用。
这符合题目的要求,所以这样建图是正确的。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3200, M = (N * 2 * 2) * 4 + 10, K = 40, INF = 1e8;
int n, m, k, S, T;
int W[K][K];
int h[N], rh[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
void add(int h[], int a, int b, int c, int d)
{
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
void add2(int h[], int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
int hh = 0, tt = 1;
memset(d, -0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = INF;
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (f[i] && d[ver] < d[t] + w[i])
{
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(incf[t], f[i]);
if (!st[ver])
{
q[tt ++ ] = ver;
if (tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
int EK()
{
int cost = 0;
while (spfa())
{
int t = incf[T];
cost += d[T] * t;
for (int i = T; i != S; i = e[pre[i] ^ 1])
{
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
return cost;
}
int get(int x, int y)
{
return (x - 1) * m + y;
}
void bfs(int robot)
{
int x = 1, y = 1;
while (x != n || y != m)
{
for (int i = rh[get(x, y)]; ~i; i = ne[i])
{
int j = e[i];
if (!w[i]) continue;
w[i] -- ;
if (j == get(x, y) + 1)
{
y ++ ;
printf("%d 1\n", robot);
}
else
{
x ++ ;
printf("%d 0\n", robot);
}
break;
}
}
}
int main()
{
scanf("%d%d%d", &k, &m, &n);
S = N - 1, T = N - 2;
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &W[i][j]);
add(h, S, get(1, 1), k, 0);
add(h, get(n, m), T, k, 0);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
if (W[i][j] == 1) continue;
add(h, get(i, j), get(i, j) + n * m, INF, 0);
if (W[i][j] == 2) add(h, get(i, j), get(i, j) + n * m, 1, 1);
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
if (W[i][j] == 1) continue;
if (i < n && W[i + 1][j] != 1)
add(h, get(i, j) + n * m, get(i + 1, j), INF, 0);
if (j < m && W[i][j + 1] != 1)
add(h, get(i, j) + n * m, get(i, j + 1), INF, 0);
}
EK();
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
if (W[i][j] == 1) continue;
for (int k = h[get(i, j) + n * m]; ~k; k = ne[k])
{
int l = e[k];
if (l == get(i, j)) continue;
add2(rh, get(i, j), l, INF - f[k]);
}
}
for (int i = 1; i <= k; i ++ )
bfs(i);
return 0;
}