bzoj2668 [cqoi2012]交換棋子
Description
有一個 \(n\) 行 \(m\) 列的黑白棋盤,你每次可以交換兩個相鄰格子(相鄰是指有公共邊或公共頂點)中的棋子,最終達到目標狀態。要求第 \(i\) 行第 \(j\) 列的格子只能參與 \(m_{i,j}\)次交換。
Input
第一行包含兩個整數 \(n,m(1\le n, m\le 20)\) 。以下 \(n\) 行為初始狀態,每行為一個包含 \(m\) 個字符的 \(01\) 串,其中 \(0\) 表示黑色棋子, \(1\) 表示白色棋子。以下 \(n\) 行為目標狀態,格式同初始狀態。以下 \(n\) 行每行為一個包含 \(m\) 個 \(0\) ~ \(9\) 數字的字符串,表示每個格子參與交換的次數上限。
Output
輸出僅一行,為最小交換總次數。如果無解,輸出 \(-1\) 。
Sample Input
3 3
110
000
001
000
110
100
222
222
222
Sample Output
4
Solution
有趣的費用流題。
非常顯然,\(S\) 要連原圖中的黑點,新圖中的黑點要連 \(T\),關鍵是中間的邊怎麽搞。
於是考慮拆點,最開始一拆二發現好像不可做。最後發現一拆三是可行的。
把 \(x\) 拆成 \(x_1,x_2,x_3\) 。
其中
- \(<x1, x2>\) ,\(x\) 最多流入的流量
- \(<x2, x3>\) ,\(x\) 最多流出的流量
對於每個 \(x\)
- 在原圖中是黑點,在新圖中是白點
- \(<x_1, x_2>:capacity=\frac{use[x]}{2};cost=0\)
\(<x_2, x_3>:capacity=\frac{use[x]+1}{2};cost=0\)
- 在原圖中是白點,在新圖中是黑點
- \(<x_1, x_2>:capacity=\frac{use[x] + 1}{2};cost=0\)
\(<x_2, x_3>:capacity=\frac{use[x]}{2};cost=0\)
- 在原圖和新圖中狀態相同
- \(<x_1, x_2>:capacity=\frac{use[x]}{2};cost=0\)
\(<x_2, x_3>:capacity=\frac{use[x]}{2};cost=0\)
還有點之間的連邊。對於連通的 \(x,y\) , \(<x_3,y_1>:capacity=INF;cost = 1\)
註意判斷一下無解的情況就萬事大吉了。
#include<bits/stdc++.h>
using namespace std;
#define N 10001
#define INF 2000000000
#define rep(i, a, b) for (int i = a; i <= b; i++)
char st[21][21], ed[21][21], use[21][21];
int n, m, ans, flow;
int S, T;
struct edge { int u, v, c, w, next; }e[100001];
int head[N], tot = 1;
int q[N], dis[N], pre[N];
bool inq[N];
inline void insert(int u, int v, int c, int w) { e[++tot].u = u, e[tot].v = v, e[tot].c = c, e[tot].w = w, e[tot].next = head[u], head[u] = tot; }
inline void add(int u, int v, int c, int w) { insert(u, v, c, w), insert(v, u, 0, -w); }
inline bool spfa() {
rep(i, S, T) dis[i] = INF; dis[S] = 0;
int l = 1, r = 1; q[1] = S;
while (l <= r) {
int u = q[l++]; inq[u] = 0;
for (int i = head[u], v, w; i; i = e[i].next) {
if (e[i].c > 0 && dis[v = e[i].v] > dis[u] + (w = e[i].w)) {
dis[v] = dis[u] + w, pre[v] = i;
if (!inq[v]) q[++r] = v, inq[v] = 1;
}
}
}
return dis[T] != INF;
}
inline void mcf() {
int d = INF;
for (int i = T; i != S; i = e[pre[i]].u) d = min(d, e[pre[i]].c);
flow += d;
for (int i = T; i != S; i = e[pre[i]].u) e[pre[i]].c -= d, e[pre[i] ^ 1].c += d, ans += d * e[pre[i]].w;
}
int main() {
cin >> n >> m; T = n * m * 3 + 1;
rep(i, 1, n) scanf("%s", st[i] + 1);
rep(i, 1, n) scanf("%s", ed[i] + 1);
rep(i, 1, n) scanf("%s", use[i] + 1);
int black = 0, white = 0;
rep(i, 1, n) rep(j, 1, m) {
int x1 = (i - 1) * m + j, x2 = x1 + n * m, x3 = x2 + n * m, Use = use[i][j] - '0';
if (st[i][j] == '1' && ed[i][j] == '0')
black++, add(S, x2, 1, 0), add(x1, x2, Use / 2, 0), add(x2, x3, (Use + 1) / 2, 0);
else if (st[i][j] == '0' && ed[i][j] == '1')
white++, add(x2, T, 1, 0), add(x1, x2, (Use + 1) / 2, 0), add(x2, x3, Use / 2, 0);
else
add(x1, x2, Use / 2, 0), add(x2, x3, Use / 2, 0);
if ((i ^ 1) && (j ^ 1)) add(x3, (i - 2) * m + j - 1, INF, 1);
if (i ^ 1) add(x3, (i - 2) * m + j, INF, 1);
if ((i ^ 1) && (j ^ m)) add(x3, (i - 2) * m + j + 1, INF, 1);
if (j ^ 1) add(x3, (i - 1) * m + j - 1, INF, 1);
if (j ^ m) add(x3, (i - 1) * m + j + 1, INF, 1);
if ((i ^ n) && (j ^ 1)) add(x3, i * m + j - 1, INF, 1);
if (i ^ n) add(x3, i * m + j, INF, 1);
if ((i ^ n) && (j ^ m)) add(x3, i * m + j + 1, INF, 1);
}
if (black ^ white) { puts("-1"); return 0; }
while (spfa()) mcf();
cout << (flow == black ? ans : -1);
return 0;
}
bzoj2668 [cqoi2012]交換棋子