洛谷传送门
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 n,m( 1 ≤ n , m ≤ 20 1\le n, m\le 20 1≤n,m≤20)。以下 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 0∼9数字的字符串,表示每个格子参与交换的次数上限。
输出格式:
输出仅一行,为最小交换总次数。如果无解,输出 − 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();
}