【BZOJ2668】交换棋子(CQOI2012)-费用流+拆点
测试地址:交换棋子
做法:本题需要使用费用流+拆点。
容易想到,把全部白色棋子都移动到目标位置的话,剩下的黑色棋子也自然都移动到目标位置了,因此我们只考虑白色棋子。又注意到一枚棋子的移动可以看成一份流量,这样问题就转化成了网络流问题,因此我们要考虑如何将题目中的限制条件表示出来。
注意到,一枚棋子在移动的路径中,交换的次数是它经过的点数,而对每个点的影响是,对于起点和终点使用了次交换,对于路径中的其他点使用了次交换。我们发现这个很难用传统的拆点办法来搞定,因为点作为起点,终点,起始点时的贡献是不一样的。我们进一步思考,发现这三种情况等价于:仅从该点出,仅进入该点,既进入该点又从该点出。因此我们把一个点拆成三个点,在它们之间顺次连边,如果从第一个点进入第二个点,表示路径进入了该点,如果从第二个点进入第三个点,表示路径从该点出,这样我们就能表现它们的贡献了。考虑三种情况:
1.该点起始状态和终止状态相同,那么该点的第二个点的入度和出度应该相等,所以两条边的容量限制均为。
2.该点上原来是白子,需要变成黑子,那么该点的第二个点的出度比入度大,所以两条边的容量应该依次为。
3.该点上原来是黑子,需要变成白子,那么该点的第二个点的入度比出度大,所以两条边的容量应该依次为。
这样我们就把每个点交换次数的限制表现了出来。那么我们再开一个源点和一个汇点,从源点向所有原来是白子的点的第二个点连容量为的边,从所有要变成白子的点的第二个点向汇点连容量为的边,再从每个点的第三个点向与它相邻的点的第一个点连容量为的边,这样这个网络的每一种流都对应着一种合法的方案了。
那么怎么求最小的解呢?只需要把第三个点向第一个点连的边附上的费用,做费用流即可。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,S,T,first[2010]={0},tot=1,sum1=0,sum2=0;
int dis[2010],last[2010],laste[2010];
char in[21][21],out[21][21],val[21][21];
bool vis[2010]={0};
struct edge
{
int v,next,f,c;
}e[100010];
queue <int> Q;
int point(int x,int y,int type)
{
return (x-1)*m+y+type*n*m;
}
void insert(int a,int b,int f,int c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}
void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",in[i]+1);
for(int i=1;i<=n;i++)
scanf("%s",out[i]+1);
for(int i=1;i<=n;i++)
scanf("%s",val[i]+1);
S=3*n*m+1,T=3*n*m+2;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if (in[i][j]-'0') insert(S,point(i,j,1),1,0),sum1++;
if (out[i][j]-'0') insert(point(i,j,1),T,1,0),sum2++;
int limit=val[i][j]-'0';
if (!(in[i][j]-'0')&&out[i][j]-'0') insert(point(i,j,0),point(i,j,1),(limit+1)/2,0);
else insert(point(i,j,0),point(i,j,1),limit/2,0);
if (in[i][j]-'0'&&!(out[i][j]-'0')) insert(point(i,j,1),point(i,j,2),(limit+1)/2,0);
else insert(point(i,j,1),point(i,j,2),limit/2,0);
for(int ki=-1;ki<=1;ki++)
for(int kj=-1;kj<=1;kj++)
{
if (ki==0&&kj==0) continue;
if (i+ki<1||i+ki>n||j+kj<1||j+kj>m) continue;
insert(point(i,j,2),point(i+ki,j+kj,0),inf,1);
}
}
}
bool spfa()
{
for(int i=1;i<=T;i++)
dis[i]=inf;
dis[S]=0;
Q.push(S);
vis[S]=1;
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[v]+e[i].c<dis[e[i].v])
{
dis[e[i].v]=dis[v]+e[i].c;
last[e[i].v]=v;
laste[e[i].v]=i;
if (!vis[e[i].v])
{
vis[e[i].v]=1;
Q.push(e[i].v);
}
}
vis[v]=0;
}
return dis[T]!=inf;
}
void mincost()
{
if (sum1!=sum2)
{
printf("-1");
return;
}
int maxf=0,ans=0;
while(spfa())
{
int x=T,minf=inf;
while(x!=S)
{
minf=min(minf,e[laste[x]].f);
x=last[x];
}
x=T;
while(x!=S)
{
e[laste[x]].f-=minf;
e[laste[x]^1].f+=minf;
x=last[x];
}
maxf+=minf;
ans+=minf*dis[T];
}
if (maxf<sum1) printf("-1");
else printf("%d",ans);
}
int main()
{
init();
mincost();
return 0;
}