【bzoj2668】[cqoi2012]交换棋子 费用流
题目描述
有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第i行第j列的格子只能参与mi,j次交换。
输入
第一行包含两个整数n,m(1<=n, m<=20)。以下n行为初始状态,每行为一个包含m个字符的01串,其中0表示黑色棋子,1表示白色棋子。以下n行为目标状态,格式同初始状态。以下n行每行为一个包含m个0~9数字的字符串,表示每个格子参与交换的次数上限。
输出
输出仅一行,为最小交换总次数。如果无解,输出-1。
样例输入
3 3
110
000
001
000
110
100
222
222
222
样例输出
4
题解
费用流
设 $a_{i,j}=[(i,j)原来为白色]$ ,$b_{i,j}=[(i,j)后来为白色]$ 。
显然可以只考虑白点的移动,转化为费用流问题:
$S$ 向 $a_{i,j}=1$ 的点连边,容量为1,费用为0;$b_{i,j}=1$ 的点向T连边,容量为1,费用为0;相邻的点之间互相连边,容量为inf,费用为1。最小费用最大流即为答案。
但是这样有一个问题:给定的度数限制是入度+出度的限制,而不是分别的限制,这样无法直接拆点限制度数。
考虑,对于一个点,除去可能存在的 $S$ 到其的入边 及 其到 $T$ 的出边 以外,其它的一定入度=出度。因此入度就是 $\lfloor\frac{m_{i,j}-a_{i,j}-b_{i,j}}2\rfloor+b_{i,j}=\lfloor\frac{m_{i,j}-a_{i,j}+b_{i,j}}2\rfloor$ ,出度同理。
这样就能够求出一个点具体的入度出度限制,把一个点拆成3个:入点、中间点和出点。入点向中间点连边,容量为入度;中间点向出点连边,容量为出度。这样就限制了度数。
时间复杂度 $O(费用流)$
注意题目中的“连通”指的是八连通(一开始当成四连通卡了好久。。。)
#include <queue> #include <cstdio> #include <cstring> #define N 1210 #define M 121000 #define inf 1 << 30 #define pos(i , j , k) (k * n * m + (i - 1) * m + j) using namespace std; queue<int> q; int a[25][25] , b[25][25] , c[25][25] , head[N] , to[M] , val[M] , cost[M] , next[M] , cnt = 1 , s , t , dis[N] , from[N] , pre[N]; inline void add(int x , int y , int v , int c) { to[++cnt] = y , val[cnt] = v , cost[cnt] = c , next[cnt] = head[x] , head[x] = cnt; to[++cnt] = x , val[cnt] = 0 , cost[cnt] = -c , next[cnt] = head[y] , head[y] = cnt; } bool spfa() { int x , i; memset(from , -1 , sizeof(from)); memset(dis , 0x3f , sizeof(dis)); dis[s] = 0 , q.push(s); while(!q.empty()) { x = q.front() , q.pop(); for(i = head[x] ; i ; i = next[i]) if(val[i] && dis[to[i]] > dis[x] + cost[i]) dis[to[i]] = dis[x] + cost[i] , from[to[i]] = x , pre[to[i]] = i , q.push(to[i]); } return ~from[t]; } inline int rnum() { char ch = getchar(); while(ch < '0' || ch > '9') ch = getchar(); return ch ^ '0'; } int main() { int n , m , i , j , sum1 = 0 , sum2 = 0 , ans = 0; scanf("%d%d" , &n , &m) , s = 0 , t = 3 * n * m + 1; for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) a[i][j] = rnum(); for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) b[i][j] = rnum(); for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) c[i][j] = rnum(); for(i = 1 ; i <= n ; i ++ ) { for(j = 1 ; j <= m ; j ++ ) { if(!c[i][j] && a[i][j] != b[i][j]) { puts("-1"); return 0; } add(pos(i , j , 0) , pos(i , j , 1) , (c[i][j] - a[i][j] + b[i][j]) >> 1 , 0); add(pos(i , j , 1) , pos(i , j , 2) , (c[i][j] + a[i][j] - b[i][j]) >> 1 , 0); if(a[i][j]) add(s , pos(i , j , 1) , 1 , 0) , sum1 ++ ; if(b[i][j]) add(pos(i , j , 1) , t , 1 , 0) , sum2 ++ ; if(i > 1) add(pos(i , j , 2) , pos(i - 1 , j , 0) , inf , 1); if(i < n) add(pos(i , j , 2) , pos(i + 1 , j , 0) , inf , 1); if(j > 1) add(pos(i , j , 2) , pos(i , j - 1 , 0) , inf , 1); if(j < m) add(pos(i , j , 2) , pos(i , j + 1 , 0) , inf , 1); if(i > 1 && j > 1) add(pos(i , j , 2) , pos(i - 1 , j - 1 , 0) , inf , 1); if(i > 1 && j < m) add(pos(i , j , 2) , pos(i - 1 , j + 1 , 0) , inf , 1); if(i < n && j > 1) add(pos(i , j , 2) , pos(i + 1 , j - 1 , 0) , inf , 1); if(i < n && j < m) add(pos(i , j , 2) , pos(i + 1 , j + 1 , 0) , inf , 1); } } if(sum1 != sum2) puts("-1"); else { while(spfa()) { j = inf; for(i = t ; i != s ; i = from[i]) j = min(j , val[pre[i]]); sum1 -= j , ans += j * dis[t]; for(i = t ; i != s ; i = from[i]) val[pre[i]] -= j , val[pre[i] ^ 1] += j; } if(sum1) puts("-1"); else printf("%d\n" , ans); } return 0; }