方格取数问题

题目描述

在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。

输入输出格式

输入格式:
第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。

输出格式:
程序运行结束时,将取数的最大总和输出

输入输出样例

输入样例#1:
3 3
1 2 3
3 2 3
2 3 1

输出样例#1:
11

分析:这是一道经典的网络流模板题,我们先将格子黑白染色,假使我们选中一个格子,那么与之冲突的格子一定是不同颜色的,所以我们把所有黑格向源点连边,容量是格子里所带的权值,所有的白格向汇点连边,容量也是权值;每个黑格向与之有冲突的的白格连边,跑一遍最大流就好了,然而需要注意的是:最后的答案是所有权值之和-最大流
我在这里尝试的解释一下这么做的正确性,虽然算的是最大流,但换句话说就是最小割,我们割掉的边是权值最小的遍,所以最后的答案要用所有权值之和-最大流(最小割)

这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>

using namespace std;

const int INF=10000001;
struct node{
    int x,y,v,next;
}; 
node way[8001];
int n,m,st[4001],map[201][201],s,t;
int deep[4001],tot=-1,tt=0;
bool p[4001];
int zz[4][2]={{0,1},{0,-1},{1,0},{-1,0}};

int add(int u,int vv,int w)
{
    tot++;
    way[tot].x=u;   //正向边 边权=容量-当前流量 
    way[tot].y=vv;
    way[tot].v=w;
    way[tot].next=st[u];
    st[u]=tot;
    tot++;
    way[tot].x=vv;  //反向边  边权=当前流量
    way[tot].y=u;
    way[tot].v=0;
    way[tot].next=st[vv];
    st[vv]=tot;
} 

int bh(int h,int z)
{
    return m*(h-1)+z;
}

void lianbian()
{
    int i,j;
    s=0;
    t=n*m+1;
    for (i=1;i<=n;i++)
    {
        for (j=1;j<=m;j++)
        {
            if ((i+j)%2==1)
            {
               add(s,bh(i,j),map[i][j]);
               for (int l=0;l<4;l++)
               {
                    int xx=i+zz[l][0];
                    int yy=j+zz[l][1];
                    if (xx>0&&yy>0&&xx<=n&&yy<=m)
                        add(bh(i,j),bh(xx,yy),INF);
               }
            }
            else
               add(bh(i,j),t,map[i][j]);
        }
    }
}

int bfs(int s,int t)  //分层   搜索时搜的都是正向边 
{
    int i;
    memset(deep,0x7f,sizeof(deep));
    memset(p,1,sizeof(p));  //判断点是否遍历过 
    queue<int> q;
    q.push(s);
    deep[s]=1;
    p[s]=0;
    while (!q.empty())
    {
        int r=q.front();
        q.pop();
        for (i=st[r];i!=-1;i=way[i].next)
           if (p[way[i].y]&&way[i].v)  //判断一下此边是否可走 
           {
                p[way[i].y]=0;
                deep[way[i].y]=deep[r]+1;
                q.push(way[i].y);
           }
    }
    return deep[t]<0x7f;  //如果deep[t]==0x7f,说明无路可增广了 
}

int dfs(int now,int t,int limit)  //limit是可以增广的流量,因为要取min,所以传进来的是INF 
{
    int i;
    if (limit==0||now==t)  //剪枝 
       return limit;
    int flow=0;  //这张增广网上的总流量
    int f; 
    for (i=st[now];i!=-1;i=way[i].next)
    {
        if (deep[way[i].y]==deep[now]+1&&(f=dfs(way[i].y,t,min(limit,way[i].v))))   
        {  //递归放在if中!!! 
            flow+=f;
            limit-=f;  //?记住就好了 
            way[i].v-=f;
            way[i^1].v+=f;
            if (!limit) break;  //!!! 
        }
    }
    return flow;
}

int dinic()
{
    int ans=0;
    while (bfs(s,t)) //有继续增广的潜力 
       ans+=dfs(s,t,INF);  
    return ans;
}

int main()
{
    memset(st,-1,sizeof(st));
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
       for (int j=1;j<=m;j++)
          scanf("%d",&map[i][j]),tt+=map[i][j];
    lianbian();
    printf("%d",tt-dinic());
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值