zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    【WPF】操作RichTextBox(取值、赋值、清空、滚动条自动滚动实例、文本自动滚动实例)
    系统初始化 服务列表
    多个filter如何决定调用顺序
    IE浏览器 查看Form对象
    java try_catch 分析
    关于ClassLoader 和Class的俩个记录
    lis分析之一一批处理(任务)如何连接数据库的
    document.all("div).style.display = "none"与 等于""的区别
    Mybatis Util包
    Spring创建bean对象的三种方式
  • 原文地址:https://www.cnblogs.com/SYCstudio/p/7286914.html
Copyright © 2011-2022 走看看