方格取数
题目描述
在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。
输入输出格式
输入格式:
第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。
输出格式:
程序运行结束时,将取数的最大总和输出
题目链接
这道题的题意还是比较好理解的,就是不能取相邻的两个数,使所有取出来的数的总和最大。。
嗯,先不要急着想搜索、暴力或贪心(虽然有可能可以过,如果可以,欢迎指教),这题可是出自网络流24题的呀。
所以肯定是用网络流 我们也可以用网络流来做。
首先,也是最关键的,这题如何建图?
我们可以这样想:
把格子染成黑白两色,形如下图: 有点丑。。好吧,不止一点
其实黑白两色也可以反过来,不过这不是重点。
我们把每个格标个号:
然后我们可以把格子抽象成一个点,然后分开:
最后就是连边:
左边那个蓝蓝的是源点,然后那个白色的大圈是汇点。
从源点连向黑点的边的权值(容量)是那个黑点的值,然后从白点连向汇点的边也是权值。
最后中间那一大坨是。。。。嗯,边的两边的点时候不能同时选的(因为有公共边),边权是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;
}