【bzoj2668】【cqoi2012】【交换棋子】【费用流】

Description

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

Input

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

Output

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

Sample Input

3 3
110
000
001
000
110
100
222
222
222

Sample Output

4
题解:
我们只需要把黑色棋子移动到目标位置即可.
考虑一条合法的移动路径.
开始和结尾的点只交换了一次,中间的点交换了两次.
所以我们可以把每个点拆成三个点(a,b,c).
如果这个点是原图中的黑点.
a向b连流量为c[i][j]/2,费用为0的边.
b向c连流量为(c[i][j]+1)/2,费用为0的边.
S向b连流量为1,费用为0的边.
如果这个点事新图中的黑点.
a向b连流量为(c[i][j]+1)/2,费用为0的边.
b向c连流量为c[i][j]/2,费用为0的边.
b向T连流量为1,费用为0的边.
如果这个点在新图和原图中都是白点.
a向b连流量为c[i][j]/2,费用为0的边.
b向c连流量为c[i][j]/2,费用为0的边.
对于相邻的两个点(x,y)
x.c向y.a连流量为inf,费用为1的边.
跑最小费用最大流即可.
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2000
#define M 50000
#define inf 707406378
using namespace std;
char map[40][40],p[50][50],s[50][50];
int n,m,T,dis[N],f[N],q[N*200],pre[N],c[50][50],sum1,sum2,ans;
int cnt(1),next[M<<1],point[N],num,ff,a[50][50],b[50][50];
int x[8]={1,0,-1,0,1,1,-1,-1},y[8]={0,1,0,-1,-1,1,-1,1};
struct use{
  int st,en,v,c;
}e[M<<1];
void add(int x,int y,int v,int c){
  next[++cnt]=point[x];point[x]=cnt;
  e[cnt].st=x;e[cnt].en=y;e[cnt].v=v;e[cnt].c=c;
  next[++cnt]=point[y];point[y]=cnt;
  e[cnt].st=y;e[cnt].en=x;e[cnt].v=0;e[cnt].c=-c;
} 
bool spfa(){
  int h(0),t(1);
  memset(dis,127/3,sizeof(dis));
  memset(f,0,sizeof(f));
  f[1]=1;dis[1]=0;q[t]=1;
  while (h<t){
    int u=q[++h];f[u]=0;
    for (int i=point[u];i;i=next[i])
     if (e[i].v&&dis[e[i].en]>dis[u]+e[i].c){
       pre[e[i].en]=i;dis[e[i].en]=dis[u]+e[i].c;
       if (!f[e[i].en]){
         f[e[i].en]=1;
         q[++t]=e[i].en;
       }
     }
  }
  return dis[T]!=inf;
}
void isap(){
 int mn=inf;
 for (int i=T;i!=1;i=e[pre[i]].st) mn=min(mn,e[pre[i]].v);
 num+=mn;
 for (int i=T;i!=1;i=e[pre[i]].st){
    e[pre[i]].v-=mn;e[pre[i]^1].v+=mn;ans+=mn*e[pre[i]].c;
 }  
}
int cal(int x,int y){return (x-1)*m+y-1;}
int main(){
  scanf("%d%d",&n,&m);T=n*m*3+2;
  for (int i=1;i<=n;i++) scanf("%s",map[i]+1);
  for (int i=1;i<=n;i++) scanf("%s",p[i]+1);
  for (int i=1;i<=n;i++) scanf("%s",s[i]+1); 
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      c[i][j]=s[i][j]-'0';
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++){
       a[i][j]=map[i][j]-'0';
       b[i][j]=p[i][j]-'0';
       if (!a[i][j]) sum1++;
       if (!b[i][j]) sum2++;
       if ((!a[i][j])&&(!b[i][j])) sum1--,sum2--,a[i][j]=b[i][j]=1;
    }
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++){
      if (!a[i][j]){
        add(cal(i,j)*3+3,cal(i,j)*3+2,(c[i][j])/2,0);
        add(cal(i,j)*3+2,cal(i,j)*3+4,(c[i][j]+1)/2,0);
        add(1,cal(i,j)*3+2,1,0);    
      }
     if (!b[i][j]){
        add(cal(i,j)*3+3,cal(i,j)*3+2,(c[i][j]+1)/2,0);
        add(cal(i,j)*3+2,cal(i,j)*3+4,(c[i][j])/2,0);
        add(cal(i,j)*3+2,T,1,0);    
     }
     if (a[i][j]&&b[i][j]){
       add(cal(i,j)*3+3,cal(i,j)*3+2,(c[i][j])/2,0);
        add(cal(i,j)*3+2,cal(i,j)*3+4,(c[i][j])/2,0);
     }
   }
  if (sum1!=sum2){return cout<<-1<<endl,0;}
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++){
      for (int k=0;k<=7;k++){
        int xx=i+x[k],yy=j+y[k];
        if (xx>n||xx<1||yy>m||yy<1) continue;
        add(cal(i,j)*3+4,cal(xx,yy)*3+3,inf,1);
      }
   }
  while (spfa()) isap();
  if (num!=sum1){return cout<<-1<<endl,0;}
  cout<<ans<<endl;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值