题意:
有一个
n
n
n行
m
m
m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第
i
i
i行第
j
j
j列的格子只能参与
m
i
,
j
m_{i,j}
mi,j次交换。求最小交换次数,如果不行就输出-1
题解:
似乎不合法的情况只有起始状态和终止状态同色棋子数不同。主要还是考虑合法情况的话如何求最优答案。
这个题真的是个特别神仙的题,是个费用流题目。这个题我也不知道怎么能想出来,因为我确实想不出来。于是就只能直接说做法了。
首先我们转化一下题意。我们把交换棋子看作白色的棋子所在的格子是空格,黑色棋子所在的格子看作有棋子,这样黑白棋子互换可以看作把一个棋子移动到了一个空格子。当然黑白反过来也是等价的。
直接考虑网络流的话,会遇到一些问题。第一是除了起点和终点之外的点都经过了两次,但是起点终点两个点只经过了一次,用流量表示经过次数会有问题。由于起点终点只经过了一次,也不能直接除以 2 2 2。于是这个题的做法是把一个格子上的每个点拆成三个点,第一个点表示进入这个格子的点,第二个表示这个格子,第三个表示出这个格子的点。我们流量表示黑色棋子,流到哪个位置就意味着哪个位置有一个黑色棋子,那么我们还要有一个费用来记录交换次数。我们的建图方法是,如果一个格子在原图上是一个黑子棋子,源点向表示这个格子的点连流量为 1 1 1,费用为 0 0 0的边。如果在目标图上某个格子上是一个黑子棋子,那么我们从这个点向汇点连流量为 1 1 1,费用为 0 0 0的边。我们对于每一个格子,从这个格子对应拆出来的入点向表示这个格子的点连流量为这个格子交换次数限制,费用为 1 1 1的边,这个格子的点向这个格子拆出来的出点连流量为流量限制,费用为 1 1 1的边。对于入点与这个点之间的流量限制,是这个点可以进入的次数,这个点与出点之间的流量限制是这个点可以与别的点交换的次数。我们发现对于不直接在某个点结束的流量,那么我们都会进入这个点并出这个点,于是每有一个棋子经过这个点,那么它的交换次数会用两次,于是流量限制应该是原来给出的交换次数限制的一半。但是这样还不完全对,我们考虑原图和最终图对应颜色不同的格子,这些格子可能可以多进入一次或者多出去一次。在流量为偶数的情况下不会有什么问题,只可能在流量为计数的时候出错。起点为黑色最终为白色的点可以走出去的可能要多,起点为白色终点为黑色的点可以走出去的可能要少。于是有一个加一再下取整,具体可以看我的代码吧。然后是每个点拆出来的出点向周围八连通的格子的入点连流量为正无穷,费用为 0 0 0的边。这样建完图跑最小费用最大流。
最后答案要除以 2 2 2,因为交换两个棋子在流的过程中会在进一个格子和出一个格子的时候算两遍费用。
说一下这样做的正确性。拆成三个点很好的保证了起点和终点只算了一次交换次数,其他点算了两次这个过程。一开始为黑色,后来是白色的格子肯定要入比出少 1 1 1,而一开始白色格子的入比出肯定要多 1 1 1,这样就不会出现流量不平衡的情况了。这样这个题就做完了。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,ji1[110][110],ji2[110][110],b[110][110],st,ed,hed[110000],cnt;
int ans,w[2010],v[2010],inq[2010],f[2010],num1,num2;
char s[110];
queue<int> q;
struct node
{
int from,to,next,c,cost;
}a[100010];
inline void add(int from,int to,int c,int cost)
{
a[++cnt].from=from;
a[cnt].to=to;
a[cnt].c=c;
a[cnt].cost=cost;
a[cnt].next=hed[from];
hed[from]=cnt;
a[++cnt].from=to;
a[cnt].to=from;
a[cnt].c=0;
a[cnt].cost=-cost;
a[cnt].next=hed[to];
hed[to]=cnt;
}
inline void bfs()
{
memset(w,0,sizeof(w));
memset(v,0x3f,sizeof(v));
q.push(st);
w[st]=2e9;
v[st]=0;
while(!q.empty())
{
int x=q.front();
q.pop();
inq[x]=0;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(a[i].c&&v[y]>v[x]+a[i].cost)
{
v[y]=v[x]+a[i].cost;
w[y]=min(a[i].c,w[x]);
f[y]=i;
if(!inq[y])
{
q.push(y);
inq[y]=1;
}
}
}
}
for(int i=f[ed];i;i=f[a[i].from])
{
a[i].c-=w[ed];
a[i^1].c+=w[ed];
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%s",s+1);
for(int j=1;j<=m;++j)
{
if(s[j]=='0')
{
ji1[i][j]=0;
++num1;
}
else
ji1[i][j]=1;
}
}
for(int i=1;i<=n;++i)
{
scanf("%s",s+1);
for(int j=1;j<=m;++j)
{
if(s[j]=='0')
{
ji2[i][j]=0;
++num2;
}
else
ji2[i][j]=1;
}
}
for(int i=1;i<=n;++i)
{
scanf("%s",s+1);
for(int j=1;j<=m;++j)
b[i][j]=s[j]-'0';
}
if(num1!=num2)
{
printf("-1\n");
return 0;
}
st=3*n*m+1;
ed=st+1;
cnt=1;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(ji1[i][j]==0)
{
if(ji2[i][j]==0)
{
add((i-1)*m+j,(i-1)*m+j+n*m,b[i][j]/2,1);
add((i-1)*m+j+n*m,(i-1)*m+j+2*n*m,b[i][j]/2,1);
}
else
{
add((i-1)*m+j,(i-1)*m+j+n*m,(b[i][j]+1)/2,1);
add((i-1)*m+j+n*m,(i-1)*m+j+2*n*m,b[i][j]/2,1);
add((i-1)*m+j+n*m,ed,1,0);
}
}
else
{
add(st,(i-1)*m+j+n*m,1,0);
if(ji2[i][j]==0)
{
add((i-1)*m+j,(i-1)*m+j+n*m,b[i][j]/2,1);
add((i-1)*m+j+n*m,(i-1)*m+j+2*n*m,(b[i][j]+1)/2,1);
}
else
{
add((i-1)*m+j,(i-1)*m+j+n*m,b[i][j]/2,1);
add((i-1)*m+j+n*m,(i-1)*m+j+2*n*m,b[i][j]/2,1);
add((i-1)*m+j+n*m,ed,1,0);
}
}
if(i-1&&j-1)
add((i-1)*m+j+2*n*m,(i-2)*m+j-1,2e9,0);
if(i-1)
add((i-1)*m+j+2*n*m,(i-2)*m+j,2e9,0);
if(i-1&&j+1<=m)
add((i-1)*m+j+2*n*m,(i-2)*m+j+1,2e9,0);
if(j-1)
add((i-1)*m+j+2*n*m,(i-1)*m+j-1,2e9,0);
if(j+1<=m)
add((i-1)*m+j+2*n*m,(i-1)*m+j+1,2e9,0);
if(i+1<=n&&j-1)
add((i-1)*m+j+2*n*m,i*m+j-1,2e9,0);
if(i+1<=n)
add((i-1)*m+j+2*n*m,i*m+j,2e9,0);
if(i+1<=n&&j+1<=m)
add((i-1)*m+j+2*n*m,i*m+j+1,2e9,0);
}
}
while(1)
{
bfs();
if(w[ed])
ans+=w[ed]*v[ed];
else
break;
}
printf("%d\n",ans/2);
return 0;
}