有一个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
#include
#include
#include
#include
#define INF 0x3f3f3f3f
#define s 0
#define maxn 1505
using namespace std;
int t;
vector g[maxn];
struct Line{
int r, flow, dis;
Line(int r, int flow, int dis): r(r), flow(flow), dis(dis){ }
};
vector 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 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;
}