【bzoj3774】【最优选择】【最小割】

Description

小N手上有一个N*M的方格图,控制某一个点要付出Aij的代价,然后某个点如果被控制了,或者他周围的所有点(上下左右)都被控制了,那么他就算是被选择了的。一个点如果被选择了,那么可以得到Bij的回报,现在请你帮小N选一个最优的方案,使得回报-代价尽可能大。

Input

第一行两个正整数N,M表示方格图的长与宽。

接下来N行每行M个整数Aij表示控制的代价。

接下来N行每行M个整数Bij表示选择的回报。

Output

一个整数,表示最大的回报-代价(如果一个都不控制那么就是0)。

Sample Input

3 3
1 100 100
100 1 100
1 100 100
2 0 0
5 2 0
2 0 0

Sample Output

8

HINT

对于100%的数据,N,M<=50,Aij,Bij都是小于等于100的正整数。

题解:
           首先我们可以发现如果选了周围四个点那一定不会选中间这个点,
           这样我们就可以把方案变成两种:
            1.选中间的点,
            2.选四周的点,不选中间的点. 
           这很像文理分科,唯一不一样的地方在于一种限制里面选和不选都有.
           所以我们可以对矩阵黑白染色,让白点和黑点的源汇意义相反.然后就可以用文理分科的方法做了.
代码:
#include<iostream>
#include<cstdio> 
#include<cstring>
#define N 6000
#define M 300000
#define inf 210000000
using namespace std;
int x,n,m,T,ans,point[N],next[M<<1],cnt=1,dx[]={1,0,-1,0},dy[]={0,1,0,-1};
int cur[N],pre[N],dis[N],gap[N];
struct use{
  int st,en,v;
}e[M<<1];
int read(){
  int x(0);char ch=getchar();
  while (ch<'0'||ch>'9') ch=getchar();
  while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
  return x;
}
void add(int x,int y,int v){
  next[++cnt]=point[x];point[x]=cnt;
  e[cnt].st=x;e[cnt].en=y;e[cnt].v=v;
  next[++cnt]=point[y];point[y]=cnt;
  e[cnt].st=y;e[cnt].en=x;e[cnt].v=0;
}
int cal(int x,int y){return (x-1)*m+y;}
int isap(){
  int mn,i,u(1),ans(0);gap[0]=T;
  for (int i=1;i<=T;i++) cur[i]=point[i];    
  while (dis[1]<T){
    bool f=false;
    for (i=cur[u];i;i=next[i])
      if (e[i].v&&dis[u]==dis[e[i].en]+1){f=true;cur[u]=i;break;}
    if (f){
      pre[u=e[i].en]=i;
      if (u==T){
        mn=inf;
        for (int i=T;i!=1;i=e[pre[i]].st) mn=min(mn,e[pre[i]].v);
        ans+=mn;
        for (int i=T;i!=1;i=e[pre[i]].st) e[pre[i]].v-=mn,e[pre[i]^1].v+=mn;
        u=1;
      }
    } 
    else{
      gap[dis[u]]--;if (!gap[dis[u]]) return ans;
      for (mn=T,i=point[u];i;i=next[i]) if (e[i].v) mn=min(mn,dis[e[i].en]);
      gap[dis[u]=mn+1]++;cur[u]=point[u];if (u!=1) u=e[pre[u]].st;
    }
  }
  return ans;
}
int main(){
  n=read();m=read();T=n*m+n*m+2;
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++){  
      x=read();
      if (i+j&1) add(1,cal(i,j)+1,x);
      else add(cal(i,j)+1,T,x);
    }
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++){
      x=read();ans+=x+x;
      if (i+j&1){
        add(cal(i,j)+1,T,x);
        add(1,cal(i,j)+1+n*m,x);
        add(cal(i,j)+1+n*m,cal(i,j)+1,inf);
        for(int k=0;k<4;k++){
          int xx=i+dx[k],yy=j+dy[k];
          if (xx<1||xx>n||yy<1||yy>m) continue;
          add(cal(i,j)+1+n*m,cal(xx,yy)+1,inf);
        }
      }
      else{
        add(1,cal(i,j)+1,x);
        add(cal(i,j)+1+n*m,T,x);
        add(cal(i,j)+1,cal(i,j)+1+n*m,inf);
        for (int k=0;k<4;k++){
          int xx=i+dx[k],yy=j+dy[k];
          if (xx<1||xx>n||yy<1||yy>m) continue;
          add(cal(xx,yy)+1,cal(i,j)+1+n*m,inf);
        }
      }
    }
   ans-=isap();
   cout<<ans<<endl;
} 


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值