【BZOJ 3894】 文理分科

3894: 文理分科

Time Limit: 10 Sec Memory Limit: 512 MB
Submit: 194 Solved: 122
[Submit][Status][Discuss]
Description

文理分科是一件很纠结的事情!(虽然看到这个题目的人肯定都没有纠
结过)
小P所在的班级要进行文理分科。他的班级可以用一个n*m的矩阵进行
描述,每个格子代表一个同学的座位。每位同学必须从文科和理科中选择
一科。同学们在选择科目的时候会获得一个满意值。满意值按如下的方式
得到:
1.如果第i行第秒J的同学选择了文科,则他将获得art[i][j]的满意值,如
果选择理科,将得到science[i][j]的满意值。
2.如果第i行第J列的同学选择了文科,并且他相邻(两个格子相邻当且
仅当它们拥有一条相同的边)的同学全部选择了文科,则他会更开
心,所以会增加same_art[i][j]的满意值。
3.如果第i行第j列的同学选择了理科,并且他相邻的同学全部选择了理
科,则增加same_science[i]j[]的满意值。
小P想知道,大家应该如何选择,才能使所有人的满意值之和最大。请
告诉他这个最大值。
Input

第一行为两个正整数:n,m
接下来n术m个整数,表示art[i][j];
接下来n术m个整数.表示science[i][j];
接下来n术m个整数,表示same_art[i][j];
Output

输出为一个整数,表示最大的满意值之和
Sample Input

3 4

13 2 4 13

7 13 8 12

18 17 0 5

8 13 15 4

11 3 8 11

11 18 6 5

1 2 3 4

4 2 3 2

3 1 0 4

3 2 3 2

0 2 2 1

0 2 4 4
Sample Output

152
HINT

样例说明

1表示选择文科,0表示选择理科,方案如下:

1 0 0 1

0 1 0 0

1 0 0 0

N,M<=100,读入数据均<=500

最小割。

对于这种两个中选一个求最优解的就想到最小割了。

先把答案赋值为所有读入数字之和。

每个人向源点连文科的边,向汇点连理科的边,割掉哪边表示不选哪科。

然后再对每个人分别建一个“文科点”和“理科点”。

源点连向“文科点”,“文科点”连向本人和周围的人( inf ),只要有一个人选了理科,这条路就联通了,需要割掉。

“理科点”同理。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#define M 50005
#define inf 0x3f3f3f3f
using namespace std;
int fx[10][5];
int tot,n,m,cur[M],h[M],d[M],v[M],s,t;
struct edge
{
    int from,to,cap,flow,ne;
}E[M*10];
int C(int x,int y)
{
    return (x-1)*m+y;
}
int ok(int x,int y)
{
    if (x>0&&y>0&&x<=n&&y<=m) return 1;
    return 0;
}
void Addedge(int from,int to,int cap)
{
    E[++tot]=(edge){from,to,cap,0,h[from]};
    h[from]=tot;
    E[++tot]=(edge){to,from,0,0,h[to]};
    h[to]=tot;
}
int bfs()
{
    for (int i=s;i<=t;i++)
        v[i]=0;
    d[s]=0;
    queue<int> q;
    q.push(s),v[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=h[x];i;i=E[i].ne)
        {
            edge e=E[i];
            if (!v[e.to]&&e.cap>e.flow)
            {
                q.push(e.to);
                v[e.to]=1;
                d[e.to]=d[x]+1;
            }
        }
    }
    return v[t];
}
int dfs(int x,int a)
{
    if (x==t||!a) return a;
    int flow=0;
    for (int i=h[x];i;i=E[i].ne)
    {
        edge &e=E[i];
        if (d[e.to]!=d[x]+1) continue;
        int f=dfs(e.to,min(a,e.cap-e.flow));
        if (f>0)
        {
            flow+=f;
            a-=f;
            e.flow+=f;
            E[i^1].flow-=f;
            if (!a) break; 
        }
    }
    return flow;
}
int dinic()
{
    int flow=0;
    while (bfs())
    {
        for (int i=s;i<=t;i++)
            cur[i]=h[i];
        flow+=dfs(s,inf);
    }
    return flow;
}
int main()
{
    tot=1;
    scanf("%d%d",&n,&m);
    int tot=0;
    s=0,t=n*m*3+1;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            int x;
            scanf("%d",&x);
            Addedge(s,C(i,j),x);
            tot+=x;
        }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            int x;
            scanf("%d",&x);
            Addedge(C(i,j),t,x);
            tot+=x;
        }
    fx[1][1]=fx[2][1]=0,fx[1][2]=1,fx[2][2]=-1;
    fx[3][2]=fx[4][2]=0,fx[3][1]=1,fx[4][1]=-1;
    fx[5][1]=fx[5][2]=0;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            int x;
            scanf("%d",&x);
            tot+=x;
            Addedge(s,n*m+C(i,j),x);
            for (int k=1;k<=5;k++)
            {
                int nx=i+fx[k][1],ny=j+fx[k][2];
                if (ok(nx,ny)) Addedge(n*m+C(i,j),C(nx,ny),inf);
            }
        }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            int x;
            scanf("%d",&x);
            tot+=x;
            Addedge(2*n*m+C(i,j),t,x);
            for (int k=1;k<=5;k++)
            {
                int nx=i+fx[k][1],ny=j+fx[k][2];
                if (ok(nx,ny)) Addedge(C(nx,ny),2*n*m+C(i,j),inf);
            }
        }
    cout<<tot-dinic()<<endl;
    return 0;
}

这里写图片描述

一开始忘记向本人连边了。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值