Libre 6007 「网络流 24 题」方格取数 / Luogu 2774 方格取数问题 (网络流,最大流)

Libre 6007 「网络流 24 题」方格取数 / Luogu 2774 方格取数问题 (网络流,最大流)

Description

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

Input

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

Output

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

Sample Input

3 3
1 2 3
3 2 3
2 3 1

Sample Output

11

Http

Libre:https://loj.ac/problem/6007
Luogu:https://www.luogu.org/problem/show?pid=2774

Source

网络流,最大流

解决思路

总觉的这道题能想到用网络流解很玄学
网络流的另外一种模型:点覆盖模型,就是有诸如两个只能选一个的限制这类,没有做过的话根本想不到。
我们把网格黑白染色,让黑格周围都是白格,白格同理,简单点来说对于格子(i,j),若i%2==j%2则黑色,否则白色。
另外建立一个源点一个汇点。对于每一个黑色的格子,从源点连一条容量为格子上的数的边,并且向周围的所有相邻白格子连一条容量为无穷大的边。对于每一个白格子,从白格子连一条容量位格子上的数的边到汇点。
同时,统计所有格子上的数之和 记为sum。求出最大流flow,则答案就是flow-sum。
这种解法的原理是基于最小割最大流,由于笔者知识水平不高,暂且不知道如何证明。但可以通过画图手动模拟的方式基本上可以认识到其正确性,即通过巧妙的方式割去其中部分边点,使得剩下的最大(笔者也只能理解到这里了)
另:这里使用Dinic实现最大流,具体可以移步我的这篇文章

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

#define pos(x,y) (x-1)*n+y

const int maxN=40;
const int inf=2147483647;

class Edge
{
public:
    int u,v,flow;
};

int n,m;
int cnt=-1;
int Mat[maxN][maxN];
int Head[maxN*maxN];
int Next[maxN*maxN*maxN*maxN];
Edge E[maxN*maxN*maxN*maxN];
int depth[maxN*maxN];
int cur[maxN*maxN];
int Q[maxN*maxN*maxN];

void Add_Edge(int u,int v,int flow);
bool bfs();
int dfs(int u,int flow);

int main()
{
    memset(Head,-1,sizeof(Head));
    int sum=0;
    scanf("%d%d",&m,&n);
    for (int i=1;i<=m;i++)
        for (int j=1;j<=n;j++)
        {
            int num;
            scanf("%d",&num);
            sum+=num;//sum统计所有格子的权之和
            if (i%2==j%2)//若为黑格子
            {
                Add_Edge(0,pos(i,j),num);//连接源点
                if (i!=1)//分别连接四周的白格子,注意判断存在与否
                    Add_Edge(pos(i,j),pos(i-1,j),inf);
                if (i!=m)
                    Add_Edge(pos(i,j),pos(i+1,j),inf);
                if (j!=1)
                    Add_Edge(pos(i,j),pos(i,j-1),inf);
                if (j!=n)
                    Add_Edge(pos(i,j),pos(i,j+1),inf);
            }
            else
                Add_Edge(pos(i,j),n*m+1,num);//若为白格子,则只连接汇点
        }
    int Ans=0;
    while (bfs())//求解最大流
    {
        memcpy(cur,Head,sizeof(cur));
        while (int di=dfs(0,inf))
            Ans+=di;
    }
    cout<<sum-Ans<<endl;
    return 0;
}

void Add_Edge(int u,int v,int flow)
{
    cnt++;
    Next[cnt]=Head[u];
    Head[u]=cnt;
    E[cnt].u=u;
    E[cnt].v=v;
    E[cnt].flow=flow;

    cnt++;
    Next[cnt]=Head[v];
    Head[v]=cnt;
    E[cnt].u=v;
    E[cnt].v=u;
    E[cnt].flow=0;
}

bool bfs()
{
    memset(depth,-1,sizeof(depth));
    int h=1,t=0;
    Q[1]=0;
    depth[0]=1;
    do
    {
        t++;
        int u=Q[t];
        for (int i=Head[u];i!=-1;i=Next[i])
        {
            int v=E[i].v;
            if ((E[i].flow>0)&&(depth[v]==-1))
            {
                h++;
                Q[h]=v;
                depth[v]=depth[u]+1;
            }
        }
    }
    while (t!=h);
    if (depth[n*m+1]==-1)
        return 0;
    return 1;
}

int dfs(int u,int flow)
{
    if (u==n*m+1)
        return flow;
    for (int i=Head[u];i!=-1;i=Next[i])
    {
        int v=E[i].v;
        if ((E[i].flow>0)&&(depth[v]==depth[u]+1))
        {
            int di=dfs(v,min(flow,E[i].flow));
            if (di>0)
            {
                E[i].flow-=di;
                E[i^1].flow+=di;
                return di;
            }
        }
    }
    return 0;
}
posted @ 2017-08-04 19:55 SYCstudio 阅读(...) 评论(...) 编辑 收藏