Description

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

Input

第一行包含两个整数 \(n, m (1 \leq n, m \leq 20)\). 以下 \(n\) 行为初始状态, 每行为一个包含 \(m\) 个字符的 0-1 串, 其中 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

简单费用流拆点+双限制流量 (可能无法推广)+1A (SPFA 费用流一次写对好开心)

下面参考某位神犇的题解:http://blog.csdn.net/vmurder/article/details/44702813

首先图转化成源点往开始图的黑点 (当然你要用白点也不是不行) 流流量, 最终从结束图的黑点流向汇点. 这个应该都能想到.

然后关键是怎么在流过一次后同时限制两个点.

这也是我所想知道的……可是, 下面的方法并没有告诉我该如何实现它, 它用的是分析改流量. [请允许我做一个捂脸熊的表情]

我们分析:

如果一个点是初始图上的黑点, 那么它可以

最多可以流入 \(\lfloor \frac{限制修改次数}{2} \rfloor\), 最多可以流出 \(\lfloor \frac{限制修改次数+1}{2} \rfloor\)

如果一个点是目标图上的黑点, 那么它可以

最多可以流入 \(\lfloor \frac{限制修改次数+1}{2} \rfloor\), 最多可以流出 \(\lfloor \frac{限制修改次数}{2} \rfloor\)

否则

最多可以流入 \(\lfloor \frac{限制修改次数}{2} \rfloor\), 最多可以流出 \(\lfloor \frac{限制修改次数}{2} \rfloor\)

然后我们需要特判一下如果一个点既是初始图上的黑点, 又是目标图上上的黑点, 那么显然 它的流入流出都应该跟 否则那一部分一样. 因为流入流出以后跟正常的点一样了么.

然后费用什么乱搞就行了, 想设哪就设哪 (但是乱设的话最终答案可能需要/2)

注意是八连通.

还有另一位大神的题解:http://timeplayer.blog.163.com/blog/static/20371825420151292735677/

首先把每个点拆点, 然后把每个格子看成一个 “交换站”, 把初始图的 1 看成一个 “入”, 把目标图的 1 看成一个 “出”

那么问题就是流从入到出经过一些交换站, 使得交换站被经过的次数最少.

我们认为流不能在交换站停留, 所以流经过交换站一定是会付出代价 2: 交换进来一次, 交换出去一次.

然后注意有 “出” 或者 “入” 的点比较特殊, 因为这个点必然要经过自己的交换站一次, 而且这次经过是不需要代价的,

所以把每个位置拆成点 1 和 2, 连边 1->2 容量是: (可交换次数+[这个点是 “入”] +[这个点是 “出”] ) /2, 费用是 2.

如果这个点是入, 那么 S->1 容量是 1, 费用是-1

如果这个点是出, 那么 2->T 容量是 1, 费用是-1

最后注意判一下无解, 问题解决.

Source Code


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <queue>
#include <cstring>

using namespace std;
typedef long long lli;
const int maxn = 1500, maxm = 10010, maxn_2 = 25;
const int infinit = 0x003f3f3f;

const int move_x[8] = {  1,  0, -1, -1, -1,  0,  1,  1 },
          move_y[8] = {  1,  1,  1,  0, -1, -1, -1,  0 };

class SpfaMaxFlow
{
public:
    struct edge
    {
        int u, v, flow, cost;
        edge *next, *rev;
    };
    edge *edges[maxn], *from[maxn], epool[maxm];
    int n, s, t, ecnt;
    void add_edge(int u, int v, int flow, int cost)
    {
        // printf("add edge %d %d %d %d\n", u, v, flow, cost);
        // printf("add edge %d %d %d %d\n", v, u, 0, -cost);
        edge *p = &epool[++ecnt],
             *q = &epool[++ecnt];
        p->u = u; p->v = v; p->flow = flow; p->cost = cost;
        p->next = edges[u]; edges[u] = p; p->rev = q;
        q->u = v; q->v = u; q->flow = 0; q->cost = -cost;
        q->next = edges[v]; edges[v] = q; q->rev = p;
        return ;
    }
    int dist[maxn];
    bool inque[maxn];
    bool bfs_spfa(void)
    {
        queue<int> que;
        for (int i = 0; i <= n; i++)
            inque[i] = false, dist[i] = infinit, from[i] = NULL;
        que.push(s);
        inque[s] = true, dist[s] = 0;
        while (!que.empty()) {
            int p = que.front();
            que.pop();
            for (edge *ep = edges[p]; ep; ep = ep->next)
                if (ep->flow && dist[p] + ep->cost < dist[ep->v]) {
                    dist[ep->v] = dist[p] + ep->cost;
                    from[ep->v] = ep;
                    if (!inque[ep->v]) {
                        que.push(ep->v);
                        inque[ep->v] = true;
                    }
                }
            inque[p] = false;
        }
        if (dist[t] < infinit)
            return true;
        return false;
    }
    int max_flow;
    int min_cost;
    void eval(void)
    {
        int tmp = 0;
        while (bfs_spfa()) {
            // Currently calculating tmp = maximum flow on graph
            tmp = infinit;
            for (edge *ep = from[t]; ep; ep = from[ep->u])
                tmp = min(tmp, ep->flow);
            // Applying flow to removal operations...
            for (edge *ep = from[t]; ep; ep = from[ep->u])
                ep->flow -= tmp,
                ep->rev->flow += tmp;
            // Evaluating cost...
            max_flow += tmp;
            min_cost += tmp * dist[t];
        }
        return ;
    }
} graph;

int read_number(void) { char ch = '\0';
    while (ch < '0' || ch > '9') ch = getchar();
    return ch - '0'; }

int n, m;
int begn[maxn_2][maxn_2], term[maxn_2][maxn_2]; // The two states of chess pieces
int id[maxn_2][maxn_2], id_cnt; // id[i][j] = n*(i-1)+j

int main(int argc, char** argv)
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            id[i][j] = ++id_cnt, id_cnt++, id_cnt++;
    // Reading more data and building graph
    graph.n = n*m*3 + 2;
    graph.s = 0;
    graph.t = n*m*3 + 1;
    int dup_count = 0; // Check if two states' chess pieces' count doesn't match
    int pnt_count = 0;
    // Given commence state
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            begn[i][j] = read_number();
            if (begn[i][j] > 0) {
                graph.add_edge(graph.s, id[i][j] + 1, 1, 0);
                dup_count++;
            }
        }
    pnt_count = dup_count; // Copy data for preservation
    // Required termination state
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            term[i][j] = read_number();
            if (term[i][j] > 0) {
                graph.add_edge(id[i][j] + 1, graph.t, 1, 0);
                dup_count--;
            }
        }
    // If it was not meant to be possible, leave it
    if (dup_count != 0) {
        // Impossible because the count doesn't match
        printf("-1\n");
        return 0;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            int flow, flow_a, flow_b;
            flow = read_number();
            if (!flow) continue;
            flow_a = begn[i][j] ? (flow + 1) / 2 : flow / 2;
            flow_b = term[i][j] ? (flow + 1) / 2 : flow / 2;
            if (begn[i][j] && term[i][j])
                flow_a = flow_b = flow / 2;
            // Adding into graph
            graph.add_edge(id[i][j], id[i][j] + 1, flow_b, 1);
            graph.add_edge(id[i][j] + 1, id[i][j] + 2, flow_a, 1);
        }
        for (int j = 1; j <= m; j++)
            for (int k = 0; k < 8; k++) {
                int x = i + move_x[k],
                    y = j + move_y[k];
                if (id[x][y])
                    graph.add_edge(id[i][j] + 2, id[x][y], infinit, 0);
            }
    }
    // Evaluating max-flow-min-cost model
    graph.eval();
    if (graph.max_flow < pnt_count) {
        // Unable to satisfy delimitations
        printf("-1");
        return 0;
    }
    printf("%d\n", graph.min_cost / 2);
    return 0;
}