【2011集训队出题】happiness

最大化班级喜悦值
通过建立图模型解决如何分配文理科以使全班同学的喜悦值总和最大,使用超级源点和超级汇点来表示选科决策,并通过复杂的边权设置确保朋友间的额外喜悦值被正确计算。

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

Hint

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

Solution

这一题的建模比较有创意。
考虑建立一个超级源点S和一个超级汇点T。
从S连边到某个点表示选文科,从某个点连边到T表示选理科,边权值为给定的喜悦值。
对于两个点都选文的情况,就新建一个中间点,从S连到中间点,边权为喜悦值,再从中间点连向这两个点,边权设为inf。
类似的可以处理处理科的情况。
这里的连边很关键,也不是很好想。
代码还是比较好懂的。

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#define P(x,y) (~-(x)*m+(y)) //快速算点坐标
#define fo(i,a,b) for (i=a;i<=b;i++)
using namespace std;
const int maxn=500000;
const int inf=10000007;
int n,m,i,j,p,x,tot,ans,s,t,cnt;
int next[maxn],head[maxn],b[maxn],c[maxn],d[maxn],q[maxn],cur[maxn];
int read(){
    int su=0;
    char c=getchar();
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9') {
        su=su*10+c-'0';
        c=getchar();
    }
    return su;
}
void plus(int x,int y,int z){ next[++tot]=head[x]; head[x]=tot; b[tot]=y; c[tot]=z;}
void add(int x,int y,int z){ plus(x,y,z); plus(y,x,0);}
int bfs(){
    int l,r,x,i;
    l=0,r=1; q[r]=s;
    memset(d,0,sizeof(d));
    d[s]=1;
    while (l<r) {
        x=q[++l];
        for (i=head[x];i!=-1;i=next[i]){
            if (c[i]!=0&&d[b[i]]==0) {
                q[++r]=b[i];
                d[b[i]]=d[x]+1;
            }
        }
    }
    if (d[t]>0) return 1;
      else return 0;
}   
int dfs(int x,int f){
    if (x==t) return f;
    for (int & i=cur[x];i!=-1;i=next[i]){
        if ((d[b[i]]==d[x]+1)&&(c[i]!=0)){
            int di=dfs(b[i],min(c[i],f));   
            if   (di>0) {
                  c[i]-=di;c[i^1]+=di;return di;}
        }
    }
    return 0;
}
int main(){
    n=read(),m=read();
    tot=-1,cnt=n*m;
    memset(head,-1,sizeof(head));
    memset(next,-1,sizeof(next));
    s=0; t=6*n*m+1;
    int sum; 
    fo(i,1,n)
      fo(j,1,m) {
        x=read();
        sum+=x;
        p=P(i,j);
        add(s,p,x);
      } 
    fo(i,1,n)
      fo(j,1,m) {
         x=read();
         sum+=x;
         p=P(i,j);
         add(p,t,x);
          }
    fo(i,1,n-1)
      fo(j,1,m) {
         x=read();
         sum+=x;
         p=P(i,j); cnt++;
         add(s,cnt,x); add(cnt,p,inf); add(cnt,p+m,inf);
      }
      fo(i,1,n-1)
        fo(j,1,m) {
         x=read();
         sum+=x;
         p=P(i,j); cnt++;
         add(cnt,t,x); add(p,cnt,inf);  add(p+m,cnt,inf);
      }
      fo(i,1,n)
        fo(j,1,m-1) {
            x=read();
            sum+=x;
            p=P(i,j); cnt++;
            add(s,cnt,x); add(cnt,p,inf); add(cnt,p+1,inf);
        }
        fo(i,1,n)
          fo(j,1,m-1){
            x=read();
            sum+=x;
            p=P(i,j); cnt++;
            add(cnt,t,x); add(p,cnt,inf); add(p+1,cnt,inf);
          }
    while (bfs()) {
        fo(i,s,t) cur[i]=head[i];
        while (int di=dfs(s,inf)) ans+=di;
    }
    printf("%d\n",sum-ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值