【洛谷】方格取数问题-网络流

传送门:洛谷-方格取数问题


题意

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


数据范围

m,n<=100


题解

    说一下本蒟蒻做这道题的历程。
    嗯,一道最大流问题。
    建模应该可以选择每个点,和与它没有公共边的点之间连一条边,然而顺次跑最大流。
    可是有一个问题,这条边的权值怎么定义,而且 n2 n 2 遍历太窒息了。
    那么我们再思考一下,可不可以在每个点与它有公共边的点之间连一条有向边?但如果不分点的情况,我们又让哪些点与源点,哪些点与汇点相连呢?
    于是可以考虑,我们将棋子分个类,先不考虑每个点的权值,我们一次能选取的最多的棋子个数是不超过n*m/2个的(若n,m均为奇数,则不超过n*m/2+1个)。而选取方法是对于第i行第j列的数,将i,j奇偶相同的分为一组,i,j奇偶不同的分为一组。这样就可以保证两个组内的点都没有公共边啦。
    我们先选择其中一组的全部点,把它们与源点之间连一条为该点权值大小的有向边,再将它们与第二组中和它们有公共边的周围的点之间,连一条权值无穷大的边,然后选择另一组的全部点,把它们和汇点之间连一条为该点权值大小的有向边。
    这里运用的知识本蒟蒻才发现:这是一个最小割问题,我们逆向思维先选取所有点,然后跑出最小点权覆盖,减去这些点权之和,得到的就是答案。
    本蒟蒻上网搜了搜,发现是这么回事:
    最大点独立集=总权-最小点权覆盖集


代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
const int N=1e4+10;
const int M=1e7+10;
int n,m,T;ll ans=0;
int val,f[102][102];
int head[N],to[M],w[M],flow[M],nxt[M],tot,d[N];
queue<int>Q;

inline int min(int x,int y)
{
    return x>y? y:x;
}

inline void lk(int u,int v,int c)
{
     to[tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot++]=c;
     to[tot]=u;nxt[tot]=head[v];head[v]=tot;w[tot++]=0;
}

inline void pre()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    T=n*m+1;
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++){
        scanf("%d",&f[i][j]);
        ans+=f[i][j];
      }
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++){
         int now=(i-1)*m+j;
         if((i&1)^(j&1) == 1){
            lk(now,T,f[i][j]);
         }else{
            lk(0,now,f[i][j]);
            if(j>1) lk(now,now-1,INF);
            if(i>1) lk(now,now-m,INF);
            if(j<m) lk(now,now+1,INF);
            if(i<n) lk(now,now+m,INF);
         }
      }
}

inline bool bfs()
{
    memset(d,-1,sizeof(d));
    d[0]=0;Q.push(0);
    while(!Q.empty()){
        int now=Q.front();Q.pop();
        for(int i=head[now];i!=-1;i=nxt[i]){
            if(d[to[i]]==-1 && w[i]>flow[i]){
                d[to[i]]=d[now]+1;
                Q.push(to[i]);
            }
        }
    }   
    if(d[T]==-1) return false;
    return true;
}

inline int dfs(int st,int ed,int f)
{
    if(st==ed) return f;
    int sum=0;
    for(int i=head[st];i!=-1;i=nxt[i]){
        if(d[to[i]]==d[st]+1 && w[i]>flow[i]){
            int ww=f-sum;
            int qw=dfs(to[i],ed,min(ww,w[i]-flow[i]));
            if(qw){
               flow[i]+=qw;
               flow[i^1]-=qw;
               sum+=qw;
               if(sum==f) return f;
            }
        }
    }
    if(sum==0) d[st]=-1;
    return sum;
}

inline void get()
{
    while(bfs()){
        int now=dfs(0,T,INF);
    //  printf("here:%d\n",now);
        ans-=now;
    }
    printf("%lld\n",ans);
}

int main(){
    pre();get();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是Python实现网络方格取数问题的代码示例: ```python from ortools.graph import pywrapgraph # 定义方格的大小和初始值 grid_size = 4 grid = [[4, 7, 8, 6], [6, 7, 3, 9], [3, 5, 2, 1], [9, 2, 8, 2]] # 创建网络图 max_flow = pywrapgraph.SimpleMaxFlow() source = 0 sink = grid_size ** 2 + 1 node_num = grid_size ** 2 + 2 # 添加源点和汇点 max_flow.AddNode(node_num) max_flow.AddArc(source, sink, float('inf')) # 添加方格中的节点和边 for i in range(grid_size): for j in range(grid_size): node_id = i * grid_size + j + 1 max_flow.AddNode(node_num) max_flow.AddArc(source, node_id, grid[i][j]) if i == 0: max_flow.AddArc(node_id, sink, 0) else: for k in range(grid_size): next_node_id = (i - 1) * grid_size + k + 1 max_flow.AddArc(node_id, next_node_id, float('inf')) if j == 0: max_flow.AddArc(node_id, sink, 0) else: for k in range(grid_size): next_node_id = i * grid_size + k + 1 max_flow.AddArc(node_id, next_node_id, float('inf')) # 求解最大 status = max_flow.Solve(source, sink) if status == max_flow.OPTIMAL: max_flow_value = max_flow.OptimalFlow() print('最大取数和为:', max_flow_value) ``` 该代码使用了Google OR-Tools库中的`SimpleMaxFlow`模块,实现了方格取数问题的最大解法。该算法将方格中的每个元素看作一个节点,并且将每个节点与其上、下、左、右的相邻节点之间的边的容量设置为无限大,确保每个节点都可以通过其相邻节点到达汇点。同时,将源点与每个节点之间的边的容量设置为该节点的初始值,保证源点只能向该节点中取出其初始值大小的数。最终通过求解网络问题,得到了最大取数和。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值