[BZOJ]2127happiness 最大权闭合图再谈

Description

高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。

Input

第一行两个正整数n,m。接下来是六个矩阵第一个矩阵为n行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学选择文科获得的喜悦值。第二个矩阵为n行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学选择理科获得的喜悦值。第三个矩阵为n-1行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i+1行第j列的同学同时选择文科获得的额外喜悦值。第四个矩阵为n-1行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i+1行第j列的同学同时选择理科获得的额外喜悦值。第五个矩阵为n行m-1列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i行第j+1列的同学同时选择文科获得的额外喜悦值。第六个矩阵为n行m-1列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i行第j+1列的同学同时选择理科获得的额外喜悦值。

Output

输出一个整数,表示喜悦值总和的最大值

Sample Input

1 2
1 1
100 110
1
1000

Sample Output

1210
【样例说明】
两人都选理,则获得100+110+1000的喜悦值。
【数据规模】
对于100%以内的数据,n,m<=100 所有喜悦值均为小于等于5000的非负整数

本题做法很多,大致有三种:

解法一:

利用最小割考虑。

对于原图中所有相邻的两个人A,B,我们建边:

s->A:cost[A文]+c[文][A][B]/2,s->B:cost[B文]+c[文][A][B]/2;

A->t:cost[A理]+c[理][A][B]/2,B->t:costB[理]+c[理][A][B]/2;

A<-->B:c[文][A][B]/2+c[理][A][B]/2

这样会出现两种割,分别对应两种相同,一种选文一种选理。

code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <ctime>
#include <cmath>
#define maxn 10000
using namespace std;
int n,m;
int s,t;
int tot=1;
int fir[200000],en[200000],nex[200000],f[200000];
void ins(int a,int b,int c,int d){
    nex[++tot]=fir[a];
    fir[a]=tot;
    en[tot]=b;
    f[tot]=c;
      
    nex[++tot]=fir[b];
    fir[b]=tot;
    en[tot]=a;
    f[tot]=d;
  
}
int ch[200][200][2];
  
int flow;
int d[200000],now[200000],num[200000],pre[200000],his[200000];
void  sap(){
    flow=0;
    for (int i=0;i<=t;i++){
        now[i]=fir[i];
        d[i]=num[i]=0;
        }
    num[0]=t;
    int aug=0x7fffffff;
    bool flag;
    int i=s;
    while (d[s]<t){
        his[i]=aug;
        flag=false;
        for (int k=now[i];k;k=nex[k])
            if (f[k]>0&&d[i]==d[en[k]]+1){
                aug=min(aug,f[k]);
                flag=true;
                now[i]=k;
                pre[en[k]]=i;
                i=en[k];
                if (i==t){
                    flow+=aug;
                    while (i!=s){
                        i=pre[i];
                        f[now[i]]-=aug;
                        f[now[i]^1]+=aug;
                        }
                    aug=0x7fffffff;
                    }
                break;
                }
        if (flag) continue;
        int k1=0,minn=t;
        for (int k=fir[i];k;k=nex[k])
            if (f[k]>0&&minn>d[en[k]]){
                k1=k;
                minn=d[en[k]];
                }
        now[i]=k1;
        if (!--num[d[i]]) return;
        d[i]=minn+1;
        num[d[i]]++;
          
        if (i!=s){
            i=pre[i];
            aug=his[i];
            }
        }
  
  
}
  
int sum=0;
int main(){
//  freopen("2127.in","r",stdin);
//  freopen("2127.out","w",stdout);
    scanf("%d%d",&n,&m);
    s=n*m+1,t=n*m+2;
    for (int i=1;i<=n;i++)           //割文  去理 
        for (int j=1;j<=m;j++){
            scanf("%d",&ch[i][j][0]);
            sum+=ch[i][j][0];
            ch[i][j][0]*=2;
            }
    for (int i=1;i<=n;i++)           //割理  去文 
        for (int j=1;j<=m;j++){
            scanf("%d",&ch[i][j][1]);
            sum+=ch[i][j][1];
            ch[i][j][1]*=2;
            }
  
  
      
    for (int i=1;i<=n-1;i++)
        for (int j=1;j<=m;j++){
            int tmp;
            scanf("%d",&tmp);
            sum+=tmp;
            ins((i-1)*m+j,i*m+j,tmp,tmp);
            ch[i][j][0]+=tmp;
            ch[i+1][j][0]+=tmp;
            }
    for (int i=1;i<=n-1;i++)
        for (int j=1;j<=m;j++){
            int tmp;
            scanf("%d",&tmp);
            sum+=tmp;
            ins((i-1)*m+j,i*m+j,tmp,tmp);
            ch[i][j][1]+=tmp;
            ch[i+1][j][1]+=tmp;
            }
      
      
      
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m-1;j++){
            int tmp;
            scanf("%d",&tmp);
            sum+=tmp;
            ins((i-1)*m+j,(i-1)*m+j+1,tmp,tmp);
            ch[i][j][0]+=tmp;
            ch[i][j+1][0]+=tmp;
            }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m-1;j++){
            int tmp;
            scanf("%d",&tmp);
            sum+=tmp;
            ins((i-1)*m+j,(i-1)*m+j+1,tmp,tmp);
            ch[i][j][1]+=tmp;
            ch[i][j+1][1]+=tmp;
            }
              
              
      
      
      
      
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++){
            ins(s,(i-1)*m+j,ch[i][j][0],0);
            ins((i-1)*m+j,t,ch[i][j][1],0);
            }
    sap();
      
    printf("%d",sum-flow/2);
  
    return 0;
}

解法二:

还是利用最小割考虑。

对于原图中所有相邻的两个人A,B,新建辅助节点C,D,建边:

s->A:cost[A文],s->B:cost[B文];

A->t:cost[A理],B->t:costB[理];

C->A:INF;C->B:INF;A->D:INF;B->D:INF;  用来帮助我们表示三种取法。(最大权闭合图)

S->C:c[文][A][B];D->T:c[理][A][B];

这样也只会出现两种割。

code by gsq:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int getx()
{
    char c;int x;
    for (c=getchar();c<'0'||c>'9';c=getchar());
    for (x=0;c>='0'&&c<='9';c=getchar())
        x=(x<<3)+(x<<1)+c-'0';
    return x;
}
bool upmin(int &a,const int &b){return a>b?a=b,1:0;}
const int INF=~0U>>1;
const int MAX_N=50050,MAX_M=1200000;
int first[MAX_N],next[MAX_M],to[MAX_M],f[MAX_M];
int tal=1;
int sum;
void tjb(int x,int y,int F){
    if (F!=INF) sum+=F;
    next[++tal]=first[x];
    first[x]=tal;
    to[tal]=y;
    f[tal]=F;
     
    next[++tal]=first[y];
    first[y]=tal;
    to[tal]=x;
    f[tal]=0;
}
int pre[MAX_N],cur[MAX_N],dis[MAX_N],gap[MAX_N],his[MAX_N];
int sap(int S,int T,int Size){
    for (int i=0;i<=Size;++i)
        cur[i]=first[i],
        dis[i]=gap[i]=0;
    int maxflow=0,aug=INF;
    int u=pre[S]=S;
    gap[0]=Size;
    while (dis[S]<Size){
        his[u]=aug;
        for (int &k=cur[u];k;k=next[k])
            if (f[k]&&dis[u]==dis[to[k]]+1) break;
        if (cur[u]){
            int v=to[cur[u]];
            upmin(aug,f[cur[u]]);
            pre[v]=u;
            u=v;
            if (u==T){
                maxflow+=aug;
                while (u!=S)
                    u=pre[u],
                    f[cur[u]]-=aug,
                    f[cur[u]^1]+=aug;
                aug=INF;
                }
            }
        else{
            int mindis=Size;
            for (int k=first[u];k;k=next[k])
                if (f[k]&&upmin(mindis,dis[to[k]]+1))
                    cur[u]=k;
            if (!--gap[dis[u]]) break;
            ++gap[dis[u]=mindis];
            u=pre[u];
            aug=his[u];
            }
        }
    return maxflow;
}
int S,T;
int n,m;
int tot;
#define idx(i,j) ((i-1)*m+j)
void mkgraph(){
    n=getx(),m=getx();
    S=0;tot=idx(n,m)+1;T=++tot;
    for (int i=1;i<=n;++i)
     for (int j=1;j<=m;++j)
        tjb(S,idx(i,j),getx());
    for (int i=1;i<=n;++i)
     for (int j=1;j<=m;++j)
        tjb(idx(i,j),T,getx());
    for (int i=1;i<=n-1;++i)
     for (int j=1;j<=m;++j){
            int tp=++tot;
            tjb(S,tp,getx());
            tjb(tp,idx(i,j),INF);
            tjb(tp,idx(i+1,j),INF);
        }
    for (int i=1;i<=n-1;++i)
     for (int j=1;j<=m;++j){
            int tp=++tot;
            tjb(tp,T,getx());
            tjb(idx(i,j),tp,INF);
            tjb(idx(i+1,j),tp,INF);
        }
    for (int i=1;i<=n;++i)
     for (int j=1;j<=m-1;++j){
            int tp=++tot;
            tjb(S,tp,getx());
            tjb(tp,idx(i,j),INF);
            tjb(tp,idx(i,j+1),INF);
        }
    for (int i=1;i<=n;++i)
     for (int j=1;j<=m-1;++j){
            int tp=++tot;
            tjb(tp,T,getx());
            tjb(idx(i,j),tp,INF);
            tjb(idx(i,j+1),tp,INF);
        }
}
int main(){
    mkgraph();
    printf("%d\n",sum-sap(S,T,tot));
}


解法三:

将原问题转化,转化为:所有人现在都选理,然后每个人选择变成文会带来多少收益或损失,利用最大权闭合图考虑。

同样的,也要新建辅助节点C,D,如下:

C->A:INF;C->B:INF;A->D:INF;B->D:INF;(C点是指全部选文科,D是指有一个选了文科就要付出“共同选理科的代价”)

剩下的利用最大权闭合图考虑,用文的获益减去理得获益,如果是正的,由s连向他,如果是负的,由他连向t。

code by LDD:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
 
int get_int(){
    int x=0;char c;int t=0;
    for (c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if (c=='-'){t=1;c=getchar();}
    for (;c>='0'&&c<='9';c=getchar()){x*=10;x+=c-48;}
    if (t) x=-x;return x;  
}
 
bool upmin(int &v,int x){if (x<v) {v=x;return true;}return false;}
const int maxn=60050,maxm=600500,inf=(1<<29);
int n,m;
 
struct path{
  int first[maxn],to[maxm],next[maxm],f[maxm],w[maxm],size,s,t;
  int putx(int x,int y,int fl)
  { //printf("%d %d %d\n",x,y,fl);
    next[++size]=first[x];
    first[x]=size;
    to[size]=y;
    f[size]=fl;
    next[++size]=first[y];
    first[y]=size;
    to[size]=x;
    f[size]=0;
  }
  int pre[maxn],now[maxn],d[maxn],num[maxn],his[maxn];
  int sap(int tot)
  {
      for (int i=s;i<=t;i++) now[i]=first[i];
      num[0]=tot;pre[s]=s;
      int i=s,aug=inf,max_flow=0;
      while (d[s]<tot)
        { his[i]=aug;
          bool flag=false;
          for (int &k=now[i];k;k=next[k])
            if (f[k]&&d[i]==d[to[k]]+1)
              {
                upmin(aug,f[k]);
                pre[to[k]]=i;
                flag=true;
                i=to[k];
                if (i==t)
                  { max_flow+=aug;
                    for (;i!=s;)
                      {
                        i=pre[i];
                        f[now[i]]-=aug;
                        f[now[i]^1]+=aug;
                      }
                     aug=inf;  
                  }
                break;
              }
          if (flag) continue;
          int min_d=tot,k1;
          for (int k=first[i];k;k=next[k])
            if (f[k])
              if (upmin(min_d,d[to[k]])) k1=k;
          num[d[i]]--;if (!num[d[i]]) break;
          d[i]=min_d+1;
          num[d[i]]++;
          now[i]=k1;
          i=pre[i];
          aug=his[i];    
        }
      return max_flow;  
  }
} map1;  
 
int x1[105][105],x2[105][105],x3[105][105],x4[105][105],x5[105][105],x6[105][105];
int zh=0;
 
int main()
{
   // freopen("e.in","r",stdin);
   // freopen("e.out","w",stdout);
    n=get_int(),m=get_int();
    map1.s=0;map1.t=5*n*m-2*n-2*m+1;map1.size=1;
    int ans=0;
    for (int i=1;i<=n;i++) 
      for (int j=1;j<=m;j++)
        x1[i][j]=get_int();
    for (int i=1;i<=n;i++) 
      for (int j=1;j<=m;j++) 
        {
          ans+=x2[i][j]=get_int();
          if (x1[i][j]>=x2[i][j]) 
            {
             zh+=x1[i][j]-x2[i][j];
             map1.putx(map1.s,(i-1)*m+j,x1[i][j]-x2[i][j]);
            }
          else map1.putx((i-1)*m+j,map1.t,x2[i][j]-x1[i][j]);
        }
    int tot=n*m;    
    for (int i=1;i<=n-1;i++)
      for (int j=1;j<=m;j++)
       {
        x3[i][j]=get_int();
        tot++;
        map1.putx(map1.s,tot,x3[i][j]);
        zh+=x3[i][j];
        map1.putx(tot,(i-1)*m+j,inf);
        map1.putx(tot,i*m+j,inf);
       }
    for (int i=1;i<=n-1;i++)
      for (int j=1;j<=m;j++)
       {
        ans+=x4[i][j]=get_int();
        tot++;
        map1.putx(tot,map1.t,x4[i][j]);
        map1.putx((i-1)*m+j,tot,inf);
        map1.putx(i*m+j,tot,inf);
       } 
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m-1;j++)
       {
        x5[i][j]=get_int();
        tot++;
        zh+=x5[i][j];
        map1.putx(map1.s,tot,x5[i][j]);
        map1.putx(tot,(i-1)*m+j,inf);
        map1.putx(tot,(i-1)*m+j+1,inf);
       }
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m-1;j++)
       {
        ans+=x6[i][j]=get_int();
        tot++;
        map1.putx(tot,map1.t,x6[i][j]);
        map1.putx((i-1)*m+j,tot,inf);
        map1.putx((i-1)*m+j+1,tot,inf);
       } 
    ans+=zh-map1.sap(tot+2);
    printf("%d\n",ans);              
}


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值