【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;
}

 

posted @ 2018-01-18 16:38  GXZlegend  阅读(433)  评论(0编辑  收藏  举报