[Luogu P3159] [BZOJ 2668] [CQOI2012]交换棋子

洛谷传送门
BZOJ传送门

题目描述

有一个 n n n m m m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第 i i i行第 j j j列的格子只能参与 m i , j m_{i,j} mi,j次交换。

输入输出格式

输入格式:

第一行包含两个整数 n , m n,m nm 1 ≤ n , m ≤ 20 1\le n, m\le 20 1n,m20)。以下 n n n行为初始状态,每行为一个包含 m m m个字符的 01 01 01串,其中 0 0 0表示黑色棋子, 1 1 1表示白色棋子。以下 n n n行为目标状态,格式同初始状态。以下 n n n行每行为一个包含 m m m 0 ∼ 9 0\sim 9 09数字的字符串,表示每个格子参与交换的次数上限。

输出格式:

输出仅一行,为最小交换总次数。如果无解,输出 − 1 -1 1

输入输出样例

输入样例#1:
3 3
110
000
001
000
110
100
222
222
222
输出样例#1:
4

解题分析

先转换一下模型, 变成将一些黑色的棋子移动到固定的位置, 那么我们将源点向原来是黑色, 最后是白色的点连流量为1, 费用为0的边, 表示要从这里开始移动一个黑色棋子, 将原来是白色, 最后是黑色的点向汇点连流量为 1 1 1, 费用为0的边, 表示最后要有一个黑色棋子移到这里来。

但这样还有一些问题: 我们发现, 如果将一个棋子从 A A A移到 B B B A A A B B B在交换过程中都只操作了一次, 但中间的那些点操作了两次,那么我们就可以把一个点拆成三个点, 分别和入度、 源汇、出度连边, 再分类讨论一下流量的限制即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <queue>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define INF 1e8
#define MX 1255
#define get(i, j, k) (i * tot + (j - 1) * m + k)
template <class C> IN C max(C a, C b) {return a > b ? a : b;}
template <class C> IN C min(C a, C b) {return a < b ? a : b;}
int n, m, cnt, S, T, tot, flow, ans, sum;
bool inq[MX];
int head[MX], pre[MX], dis[MX], del[MX], val[25][25];
char mp1[25][25], mp2[25][25], bd[25][25];
int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
struct Edge {int to, fl, len, nex;} edge[200500];
IN void add(R int from, R int to, R int fl, R int cst)
{
	edge[++cnt] = {to, fl, cst, head[from]}, head[from] = cnt;
	edge[++cnt] = {from, 0, -cst, head[to]}, head[to] = cnt;
}
IN bool isok(R int x, R int y) {return x >= 1 && x <= n && y >= 1 && y <= m;}
namespace MCMF
{
	std::queue <int> q;
	IN bool SPFA()
	{
		std::memset(dis, 63, sizeof(dis));
		dis[S] = 0; del[S] = INF; q.push(S); R int now;
		W (!q.empty())
		{
			now = q.front(); q.pop();
			for (R int i = head[now]; ~i; i = edge[i].nex)
			{
				if (dis[edge[i].to] > dis[now] + edge[i].len && edge[i].fl)
				{
					dis[edge[i].to] = dis[now] + edge[i].len;
					del[edge[i].to] = min(del[now], edge[i].fl);
					pre[edge[i].to] = i;
					if (!inq[edge[i].to]) inq[edge[i].to] = true, q.push(edge[i].to);
				}
			}
			inq[now] = false;
		}
		return dis[T] ^ dis[0];
	}
	IN void update()
	{
		R int now = T, pr;
		flow += del[T], ans += del[T] * dis[T];
		W (now ^ S)
		{
			pr = pre[now];
			edge[pr].fl -= del[T], edge[pr ^ 1].fl += del[T];
			now = edge[pr ^ 1].to;
		}
	}
	void solve()
	{
		W (SPFA()) update();
		if (flow < sum) puts("-1");
		else printf("%d", ans / 2);
	}
}
int main(void)
{
	scanf("%d%d", &n, &m); int x, y;
	std::memset(head, cnt = -1, sizeof(head));
	S = n * m * 3 + 1, T = n * m * 3 + 2; tot = n * m;
	for (R int i = 1; i <= n; ++i) scanf("%s", mp1[i] + 1);
	for (R int i = 1; i <= n; ++i) scanf("%s", mp2[i] + 1);
	for (R int i = 1; i <= n; ++i) scanf("%s", bd[i]  + 1);
	for (R int i = 1; i <= n; ++i)
	for (R int j = 1; j <= m; ++j)
	{
		val[i][j] = bd[i][j] - '0';
		if (mp1[i][j] == '1' && mp2[i][j] == '0')
		{
			add(S, get(1, i, j), 1, 0); ++sum;
			add(get(0, i, j), get(1, i, j), val[i][j] >> 1, 1);
			add(get(1, i, j), get(2, i, j), val[i][j] + 1 >> 1, 1);
		}
		else if (mp1[i][j] == '0' && mp2[i][j] == '1')
		{
			add(get(1, i, j), T, 1, 0);
			add(get(0, i, j), get(1, i, j), val[i][j] + 1 >> 1, 1);
			add(get(1, i, j), get(2, i, j), val[i][j] >> 1, 1);
		}
		else
		{
			add(get(0, i, j), get(1, i, j), val[i][j] >> 1, 1);
			add(get(1, i, j), get(2, i, j), val[i][j] >> 1, 1);
		}
		for (R int k = 0; k < 8; ++k)
		{
			x = i + dx[k], y = j + dy[k];
			if (isok(x, y)) add(get(2, i, j), get(0, x, y), INF, 0);
		}
	}
	MCMF::solve();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值