方格取数问题(网络流24题之一)

                                                                                   方格取数

题目描述

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

输入输出格式

输入格式:

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

输出格式:

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

题目链接

 

这道题的题意还是比较好理解的,就是不能取相邻的两个数,使所有取出来的数的总和最大。。

嗯,先不要急着想搜索、暴力或贪心(虽然有可能可以过,如果可以,欢迎指教),这题可是出自网络流24题的呀。

所以肯定是用网络流    我们也可以用网络流来做。

 

首先,也是最关键的,这题如何建图?

我们可以这样想:

把格子染成黑白两色,形如下图:  有点丑。。好吧,不止一点

方格取数问题(网络流24题之一)_第1张图片

其实黑白两色也可以反过来,不过这不是重点。

我们把每个格标个号:

方格取数问题(网络流24题之一)_第2张图片

 

然后我们可以把格子抽象成一个点,然后分开:

方格取数问题(网络流24题之一)_第3张图片

 

最后就是连边:

方格取数问题(网络流24题之一)_第4张图片

 

左边那个蓝蓝的是源点,然后那个白色的圈是汇点。

从源点连向黑点的边的权值(容量)是那个黑点的值,然后从白点连向汇点的边也是权值。

最后中间那一大坨是。。。。嗯,边的两边的点时候不能同时选的(因为有公共边),边权是INF(尽量大)。

我们要求的是什么呢,没错,选出来的所有点的权值最大…… 这TMD和这图有什么关系啊?

别心急,这题应该是要求最大点权独立集(注意:是点权),就等于所有点的值减去最小点权覆盖集,就是减去最小割(最大流),然后我们就可以开心地写代码了。

 

可能有些同学并不理解最后一句话是什么意思(其实一开始我也不理解)。

其实是这样的,我们相当与把点权转换为了边权,我们的目的是断开一些边,使得没有路径从源点到达汇点(为了满足题目的限制条件吗),然后我们要使断开的边的权值之和最小(断开的边就相当于是不选那个点,就是剩下的边权之和最大->这就是我们要求的答案嘛),乍一看,这不就是最小割吗?因为最小割=最大流,所以我们可以跑一遍最大流,然后用总的边权减去最大流就是我们的答案了,是不是很棒棒。

 

总结:做完这题,我对网络流的理解又加深了一层。我们要巧妙的利用网络流的建模技巧(这也是网络流的精髓),把问题转换为可以用网络流解决的问题,要多刷题,刷好题,才能掌握技巧。

 

 

上代码:

#include
using namespace std;
const int INF=0x3f3f3f3f;
const int MAXN=1000005;
const int MAXM=1000005;
int d[MAXN],n,m,p[MAXN],eid,S,T,x[MAXN],y[MAXN],z[MAXN],a[105][105],sz,sum;
struct A{  //灰常正常的最大流(Dinic)
	int v,c,next;
}e[MAXM];
void init(){
	memset(p,-1,sizeof(p));
	eid=0;
}
void add(int u,int v,int c){
	e[eid].v=v;
	e[eid].c=c;
	e[eid].next=p[u];
	p[u]=eid++;
}
void insert(int u,int v,int c){
	add(u,v,c);
	add(v,u,0);
}
int bfs(){
	memset(d,-1,sizeof(d));
	queueq;
	d[S]=0;
	q.push(S);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=p[u];i!=-1;i=e[i].next){
			int v=e[i].v;
			if(e[i].c>0&&d[v]==-1){
				d[v]=d[u]+1;
				q.push(v);	
			}
		}
	}
	return (d[T]!=-1);
}
int dfs(int u,int flow){ 
	if(u==T) return flow;
	int ret=0;
	for(int i=p[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if(e[i].c>0&&d[v]==d[u]+1){
			int tmp=dfs(v,min(flow,e[i].c));
			e[i].c-=tmp;
			e[i^1].c+=tmp;
			flow-=tmp;
			ret+=tmp;
			if(!flow) break;
		}
	}
	if(!ret) d[u]=-1;
	return ret;
}
int Dinic(){
	int ret=0;
	while(bfs()){
		ret+=dfs(S,INF);
	}
	return ret;
}
int main(){    //以下开始码风突变(中了yjq的膜法)
	init();
	scanf("%d%d", &m, &n);
	S=0;T=n*m+1;//建立源点和汇点
	for(int i = 1; i <= m; i++){
		for(int j = 1; j <= n; j++){
			scanf("%d", &a[i][j]);
			sum += a[i][j];
			sz ++;
			if((i + j) % 2){
				insert(S, sz, a[i][j]);//连向源点
				if(j < n) insert(sz, sz + 1, INF);//把有限制条件的连起来,边权注意要尽量大
				if(j > 1) insert(sz, sz - 1, INF);
				if(i < m) insert(sz, sz + n, INF);
				if(i > 1) insert(sz, sz - n, INF);
			} else {
				insert(sz,T,a[i][j]);//连向汇点
			}
		}
	} 
	printf("%d",sum - Dinic());//总的边权 - 最大流(最小割)
	return 0;
}

 

你可能感兴趣的:(网络流)