bzoj2668 [cqoi2012]交换棋子

4 篇文章 0 订阅
2 篇文章 0 订阅
Description

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

Input

第一行包含两个整数 n,m(1n,m20) 。以下 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 拆成 x1,x2,x3
其中

  • <x1,x2> <script type="math/tex" id="MathJax-Element-2917"> </script> , x 最多流入的流量
  • <x2,x3> x 最多流出的流量

对于每个 x 在原图和新图中的情况,分类连边

  • 在原图中是黑点,在新图中是白点

    • <x1,x2>:capacity=use[x]2;cost=0 <script type="math/tex" id="MathJax-Element-2922"> :capacity=\frac{use[x]}{2};cost=0</script>
    • <x2,x3>:capacity=use[x]+12;cost=0 <script type="math/tex" id="MathJax-Element-2923"> :capacity=\frac{use[x]+1}{2};cost=0</script>
  • 在原图中是白点,在新图中是黑点

    • <x1,x2>:capacity=use[x]+12;cost=0 <script type="math/tex" id="MathJax-Element-2924"> :capacity=\frac{use[x] + 1}{2};cost=0</script>
    • <x2,x3>:capacity=use[x]2;cost=0 <script type="math/tex" id="MathJax-Element-2925"> :capacity=\frac{use[x]}{2};cost=0</script>
  • 在原图和新图中状态相同

    • <x1,x2>:capacity=use[x]2;cost=0 <script type="math/tex" id="MathJax-Element-2926"> :capacity=\frac{use[x]}{2};cost=0</script>
    • <x2,x3>:capacity=use[x]2;cost=0 <script type="math/tex" id="MathJax-Element-2927"> :capacity=\frac{use[x]}{2};cost=0</script>

还有点之间的连边。对于连通的 x,y <x3,y1>:capacity=INF;cost=1 <script type="math/tex" id="MathJax-Element-2929"> :capacity=INF;cost = 1</script>

注意判断一下无解的情况就万事大吉了。

#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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值