bzoj2668: [cqoi2012]交换棋子

Problem 2668. -- [cqoi2012]交换棋子

2668: [cqoi2012]交换棋子

Time Limit: 3 Sec   Memory Limit: 128 MB
Submit: 1503   Solved: 615
[ Submit][ Status][ Discuss]

Description

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

Input

第一行包含两个整数nm(1<=n, m<=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

HINT

Source

[ Submit][ Status][ Discuss]

HOME Back

先将棋盘看成只有黑子,白子看成是空白的,只需考虑黑子
然后相同的点交换是没有意义的,所以只需要考虑黑子进入和出去的次数。

如果一个点初始和最终的状态相同,说明黑子进入和出去的次数相同,上限 w 2 \frac{w}{2} 2w
如果初始为黑子最终为白子,说明出去的次数多一次,进入上限 w − 1 2 \frac{w-1}{2} 2w1,出去上 w + 1 2 \frac{w+1}{2} 2w+1
如果初始为白子最终为黑子,说明进入的次数多一次,和上面类似
对次数的限制,可以将每个点拆成入点,原点,出点,容量为上限。

每个点的出点向八连通入点连边,S向初始黑连边,终黑向T连边

如果最大流不是黑子个数,就是-1,不然就是最小费用


#include 
#include 
#include 
using namespace std;
 
#define dd c=getchar()
int read() {int s=0,w=1;char c;while (dd,c>'9' || c<'0') if (c=='-') w=-1;while (c>='0' && c<='9') s=s*10+c-'0',dd;return s*w;}
#undef dd
void write(int x) {if (x<0) putchar('-'),x=-x;if (x>=10) write(x/10);putchar(x%10|'0');}
void wln(int x) {write(x);putchar('\n');}void wsp(int x) {write(x);putchar(' ');}
 
const int N = 25, M = 3*N*N, inf = 1010580540;
const int mx[8]={1,1,1,0,0,-1,-1,-1},my[8]={1,0,-1,1,-1,1,0,-1};
struct edge {
    int t,nxt,w,v;
}e[M*10];
struct node {
    int x,v;
    bool operator <(node a) const{
        return v > a.v;
    }
};
 
int n,m,cnt,Ct,S,T,tim,num,flow,cost;
char s1[N][N],s2[N][N],s3[N][N];
int head[M],dis[M],vis[M],h[M],pre[M];
priority_queue<node> q;
int id(int x, int y, int p) {return (x-1)*m+y+p*Ct;}
void add(int u, int t, int w, int v) {
//  printf("%d %d %d %d\n",u,t,w,v);
    e[++cnt] = (edge){t,head[u],w,v}; head[u] = cnt;
    e[++cnt] = (edge){u,head[t],0,-v}; head[t] = cnt;
}
 
bool dij() {
    ++tim;
    memset(dis, 60, sizeof dis);
    dis[S] = 0; q.push((node){S, 0});
    while (!q.empty()) {
        int u = q.top().x; q.pop();
        if (vis[u] == tim) continue;
        vis[u] = tim;
        for (int i = head[u]; i; i = e[i].nxt) {
            int t = e[i].t, v = e[i].v;
            if (e[i].w > 0 && dis[t] > dis[u]+v+h[u]-h[t]) {
                dis[t] = dis[u]+v+h[u]-h[t];
                pre[t] = i;
                q.push((node){t, dis[t]});
            }
        }
    }
//  for (int i = 1; i <= T; i++) wsp(pre[i]);
//  puts("");
    return dis[T] != inf;
}
 
int aug(int x, int Flow) {
    if (x == S) return Flow;
    int tmp = aug(e[pre[x]^1].t, min(Flow, e[pre[x]].w));
    e[pre[x]].w-=tmp; e[pre[x]^1].w+=tmp;
    return tmp;
}
void min_flow_cost() {
    while (dij()) {
        for (int i = 1; i <= T; i++) h[i]+=dis[i];
        int tmp = aug(T, inf);
        flow+=tmp;
        cost += tmp*h[T];
    }
}
int main() {
//  freopen("2668.in", "r", stdin);
//  freopen("2668.out", "w", stdout);
    n = read(); m = read();
    Ct = n*m; cnt=1;
    S = Ct*3+1; T = Ct*3+2;
    for (int i = 1; i <= n; ++i) scanf("%s",s1[i]+1);
    for (int i = 1; i <= n; ++i) scanf("%s",s2[i]+1);
    for (int i = 1; i <= n; ++i) scanf("%s",s3[i]+1);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; j++) {
            int w = s3[i][j]-'0';
            if (s1[i][j] == s2[i][j]) add(id(i,j,0), id(i,j,1), w/2, 0), add(id(i,j,1), id(i,j,2), w/2, 0);
            else if (s1[i][j] == '1') {
                add(S, id(i,j,1), 1, 0), num++;
                add(id(i,j,0), id(i,j,1), (w-1)/2, 0), add(id(i,j,1), id(i,j,2), (w+1)/2, 0);
            }
            else {
                add(id(i,j,1), T, 1, 0);
                add(id(i,j,0), id(i,j,1), (w+1)/2, 0), add(id(i,j,1), id(i,j,2), (w-1)/2, 0);
            }
            for (int k = 0; k < 8; k++) {
                int x = i+mx[k], y = j+my[k];
                if (x < 1 || x > n || y < 1 || y > m) continue;
                add(id(i,j,2), id(x,y,0), inf, 1);
            }
        }
    min_flow_cost();
    printf("%d\n",flow != num ? -1 : cost);
}

你可能感兴趣的:(费用流,费用流,拆点)