方格取数问题
题目描述
在一个有m*n个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意2个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。
输入格式
文件第1行有2个正整数m和n,分别表示棋盘的行数和列数。接下来的m行,每行有n个正整数,表示棋盘方格中的数。(0 <= m, n <= 30)
输出格式
取数的最大总和.
输入样例
33
1 2 3
3 2 3
2 3 1
输出样例
11
题目大意:
给出m*n的格子,相邻的格子的值不可同时取,最后求出最大值。
我还误以为只有两种情况,用暴力不就好了吗,不过wyy给我举出其他情况,事实证明我想少了。
构图:
如图,我们会发现,黑色格子都是可以同时选的,白色格子也是同时可以走的。会发现同是黑色格子(白色格子),它们的行列坐标加起来mod 2,都是一样的。
所以,我们可以用二分图来给它们归类。首先,行列之和,为偶数的放在左边,行列之和为奇数的放在右边。于是,便得出,黑色格子的放在右边,白色格子放在左边。
因为黑白之间是互不可取的,以中间的黑色格子为例,它不可达的格子有它上下左右的白色格子,于是把它们相连。同理,所有黑色格子都这么连。
并把黑色格子(行列和为偶数)与s节点相连,白色格子与t相连。
解题思想:
求出来的为最小割,也就是说,如果经过这个点,那就是把这个点割去了,不能选。而得出最小割,也就是得出了不选哪些格子,所以,用总和减去最小割就是最后的答案。
细节:
我们一开始一直wrong,是因为数组开小了,maxn直接开为35,可是在这一个棋盘里面,格子数是远远不止这么多的,要开到n^2。
代码如下:
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int maxn=1000,oo=10000000;//注意数组大小,格子数为n^2。 int ans; int cur=-1,s,t,m,n; int head[maxn],c[maxn][maxn],v[maxn],id[maxn][maxn],a[maxn][maxn]; int xx[5]={0,0,1,-1},yy[5]={1,-1,0,0}; struct space { int to,next,va,type; }edge[maxn*maxn]; void add(int from,int to,int va,int type) { cur++; edge[cur].to=to; edge[cur].va=va; edge[cur].type=type; edge[cur].next=head[from]; head[from]=cur; } void build(int x,int y) { for(int i=0;i<4;i++) { int nowx=x+xx[i],nowy=y+yy[i];//上下左右点的坐标 if(nowx<1||nowx>m||nowy<1||nowy>n) continue;//判断是否有出现越界的情况 add(id[x][y],id[nowx][nowy],oo,0);//相连 add(id[nowx][nowy],id[x][y],0,1); } } void init() { memset(head,-1,sizeof(head)); int k=0; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { cin>>a[i][j]; ans+=a[i][j]; k++; id[i][j]=k; c[i][j]=(i%2==j%2);//记下这个格子行列之和为偶数或是奇数 } } s=0,t=n*m+1; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { if(c[i][j])//如果为偶数,即图中的黑色格子 build(i,j); } } for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { if(c[i][j]) { add(s,id[i][j],a[i][j],0);//黑色格子与s相连,边权为a[i][j] add(id[i][j],s,0,1); } else { add(id[i][j],t,a[i][j],0); add(t,id[i][j],0,1); } } } } int dfs(int now,int mi) { if(now==t) return mi; if(v[now]==1) return 0; v[now]=1; int h=head[now]; while(h!=-1) { int to=edge[h].to,va=edge[h].va; if(va!=0) { int k; k=dfs(to,min(va,mi)); if(k!=0) { edge[h].va-=k; edge[h^1].va+=k; return k; } } h=edge[h].next; } return 0; }//最小割(与最大流的代码完全是一样的) int main() { freopen("2207.in","r",stdin); freopen("2207.out","w",stdout); cin>>m>>n; init(); while(1) { memset(v,0,sizeof(v)); int res; res=dfs(0,oo); if(res==0) break; ans-=res; } cout<<ans<<endl; return 0; }
http://blog.csdn.net/u013686535/article/details/77152103
这篇博客写得比较清楚。