洛谷 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,1) 的入点连一条容量为 k k k,费用为 0 0 0 的边。

  2. ( n , m ) (n, m) (n,m) 的出点向汇点连一条容量为 k k k,费用为 0 0 0 的边。

  3. 对于每个 ( i , j ) ( 1 ≤ i ≤ n , 1 ≤ j ≤ m , i (i, j)(1 \le i \le n,1 \le j \le m,i (i,j)(1in1jmi 不是障碍 ) ) ),如果 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 的边。

  4. 对于每个 ( i , j ) ( 1 ≤ i ≤ n , 1 ≤ j ≤ m , i (i, j)(1 \le i \le n,1 \le j \le m,i (i,j)(1in1jmi 是空地 ) ) ),从 ( i , j ) (i, j) (i,j) 的入点向 ( i , j ) (i, j) (i,j) 的出点连一条容量为 i n f inf inf,费用为 0 0 0 的边。

  5. 对于每个 ( i , j ) ( 1 ≤ i ≤ n , 1 ≤ j ≤ m , i (i, j)(1 \le i \le n,1 \le j \le m,i (i,j)(1in1jmi 是石块 ) ) ),从 ( 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值