洛谷 P3159 [CQOI2012]交换棋子(费用流)

题目描述

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

输入输出格式

输入格式:

 

第一行包含两个整数n,m(1<=n, m<=20)。以下n行为初始状态,每行为一个包含m个字符的01串,其中0表示黑色棋子,1表示白色棋子。以下n行为目标状态,格式同初始状态。以下n行每行为一个包含m个0~9数字的字符串,表示每个格子参与交换的次数上限。

 

输出格式:

 

输出仅一行,为最小交换总次数。如果无解,输出-1。

 

输入输出样例

输入样例#1: 

3 3
110
000
001
000
110
100
222
222
222

输出样例#1: 

4

解题思路

安利这个大佬的题解http://www.cnblogs.com/dedicatus545/p/8781976.html

然后下面是本蒟蒻写给自己看的题解。

看到题目第一反应是把每个棋盘格拆成两个点,之间连线流量是可以移动的次数,可是移动一次会有两个点被移动,就不行了。然后就歇菜了,发现大佬是把一个格子拆成三个点。一个点进,一个点出,一个点作为中介。源点连初始状态为1的格子的中介点,目标状态为1的格子的中介的连汇点t。

从进点进来到中介再到出点出去,如果把格子x和y上的棋子交换,那就相当于路线,x中介点->x出点,x出点->y进点,y进点->y中介点,这个过程中x移动了一次,y也移动了一次。假设一个点可以移动k次,如果这个点初始状态和结束状态一样,那么移动次数一定是双数且出和进一样(只有1和0交换才有意义),如果不一样且初始为1,那就说明这个1被移动出去了,这个点的出移动比入移动大1,如果不一样且目标状态为1,那么这个点的入移动比出移动大1。所以当奇数的时候,流量的分配方案就有了。以及,因为任意两个点之间是可以多次交换的,所以可移动两点之间的流量设为无限大。

代码如下

#include <iostream>
#include <queue>
#include <vector>
#include <cmath>
#include <cstring>
#define INF 0x3f3f3f3f
#define s 0 
#define maxn 1505
using namespace std;
int t;
vector<int> g[maxn];
struct Line{
	int r, flow, dis;
	Line(int r, int flow, int dis): r(r), flow(flow), dis(dis){	}
};
vector<Line> line;
void add_line(int x, int y, int f, int d)
{
	line.push_back(Line(y, f, d));
	g[x].push_back(line.size() - 1);
	line.push_back(Line(x, 0, -d));
	g[y].push_back(line.size() - 1);
}
int pre[maxn];
int edge[maxn];
int flow[maxn];
int dis[maxn];
bool spfa()
{
	bool vis[maxn] = {0};
	memset(pre, -1, sizeof(pre));
	memset(flow, 0x7f, sizeof(flow));
	memset(dis, 0x7f, sizeof(dis));
	queue<int> que;
	que.push(s);
	vis[s] = true;
	dis[s] = 0;
	while(!que.empty()){
		int top = que.front();
		que.pop();
		vis[top] = false;
		for(int i = 0; i < g[top].size(); i ++){
			int z = g[top][i];
			int r = line[z].r;
			if(line[z].flow && dis[top] + line[z].dis < dis[r]){
				dis[r] = dis[top] + line[z].dis;
				flow[r] = min(flow[top], line[z].flow);
				pre[r] = top;
				edge[r] = z;
				if(!vis[r]){
					vis[r] = true;
					que.push(r);
				}
			}
		}
	} 
	return pre[t] != -1;
}
int st, ed;
void EK()
{
	int max_flow = 0;
	int min_dis = 0;
	while(spfa()){
		max_flow += flow[t];
		min_dis += flow[t] * dis[t];
		int now = t;
		while(pre[now] != -1){
			line[edge[now]].flow -= flow[t];
			line[edge[now]^1].flow += flow[t];
			now = pre[now];
		}
	} 
	if(max_flow < ed)
		cout << -1 << endl;
	else
		cout << min_dis << endl;
}
int n, m;
int list[25][25];
int dx[] = {0, 0, 1, 1, 1, -1, -1, -1};
int dy[] = {1, -1, 0, 1, -1, 0, 1, -1};
void work(int x)
{
	int li = (x - 1) / m;
	int lj = (x - 1) % m;;
	for(int i = 0; i < 8; i ++){
		int u = dx[i] + li;
		int v = dy[i] + lj;
		if(u >= 0 && u < n && v >= 0 && v < m){
			//cout << x << " " << list[u][v] << endl;
			add_line(x*3+2, list[u][v]*3, INF, 1);  //可以多次交换 
		}
			
	}
}
int main()
{	
	while(cin >> n >> m){
		for(int i = 0; i < n; i ++){
			for(int j = 0; j < m; j ++)
				list[i][j] = i*m + j+1;
		}
		t = maxn - 1;
		string str[3][30];
		for(int i = 0; i < 3; i ++){
			for(int j = 0; j < n; j ++)
				cin >> str[i][j];
		}
		st = ed = 0;
		for(int i = 0; i < n; i ++){
			for(int j = 0; j < m; j ++){
				int num = i * m + j + 1;
				int cnt = str[2][i][j] - '0';
				//cout << num << " " << num*3 << " " << num*3+1 << " " << num*3+2 << endl;
				if(str[0][i][j] == str[1][i][j]){
					add_line(num*3, num*3+1, cnt/2, 0);
					add_line(num*3+1, num*3+2, cnt/2, 0);
				}
				else if(str[0][i][j] == '1'){
					add_line(num*3, num*3+1, cnt/2, 0);
					add_line(num*3+1, num*3+2, (cnt+1)/2, 0);
				}
				else {
					add_line(num*3, num*3+1, (cnt+1)/2, 0);
					add_line(num*3+1, num*3+2, cnt/2, 0);
				}
				if(str[0][i][j] == '1'){
					st ++;
					add_line(s, num*3+1, 1, 0);
				}
				if(str[1][i][j] == '1'){
					ed ++;
					add_line(num*3+1, t, 1, 0);
				}
			}
		}
		if(st != ed){
			cout << -1 << endl;
			continue;
		}
		for(int i = 1; i <= n * m; i ++){
			work(i);
		}
		EK();
		for(int i = s; i <= t; i ++)
			g[i].clear();
		line.clear();
	}
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值