题目描述
在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。
输入输出格式
输入格式:
第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。
输出格式:
程序运行结束时,将取数的最大总和输出
输入输出样例
输入样例#1:
3 3
1 2 3
3 2 3
2 3 1
输出样例#1:
11
说明
m,n<=100
分析:一道二分图的最大权独立集。显然给图进行黑白染色(即一种颜色与周围的颜色都不同),满足二分图性。那显然如果我们选了一个点,就不能选周围点。也就是源点向一个白点连一条白点权值的边,白点向黑点连inf的边,黑点向汇点连黑点权值的边。显然就是一个最小割了。于是可以先把所有点权加起来,然后减去最小割就是答案。
dalao说他也不会一般图的最大权独立集,不过一般图的最大独立集可以用带花树做(什么东西呀)。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
const int maxn=105;
const int inf=0x3f3f3f3f;
using namespace std;
int n,m,i,j,ans,cnt,s,t;
int a[maxn][maxn],ls[maxn*maxn],dis[maxn*maxn];
struct node{
int y,next,op,c;
}g[maxn*maxn*8];
queue <int> q;
void add(int x,int y,int w)
{
g[++cnt].y=y; g[cnt].c=w; g[cnt].op=cnt+1; g[cnt].next=ls[x]; ls[x]=cnt;
g[++cnt].y=x; g[cnt].c=0; g[cnt].op=cnt-1; g[cnt].next=ls[y]; ls[y]=cnt;
}
int fc(int x,int y)
{
return (x-1)*m+y;
}
bool bfs()
{
for (int i=s;i<=t;i++) dis[i]=0;
dis[s]=1;
while (!q.empty()) q.pop();
q.push(s);
while (!q.empty())
{
int u=q.front();
q.pop();
for (int i=ls[u];i>0;i=g[i].next)
{
int v=g[i].y;
if ((dis[v]==0) && (g[i].c))
{
dis[v]=dis[u]+1;
if (v==t) return true;
q.push(v);
}
}
}
return false;
}
int dfs(int x,int maxf)
{
if ((x==t) || (maxf==0)) return maxf;
int ret=0;
for (int i=ls[x];i>0;i=g[i].next)
{
int y=g[i].y;
if ((dis[x]+1==dis[y]) && (g[i].c))
{
int f=dfs(y,min(g[i].c,maxf-ret));
g[i].c-=f;
g[g[i].op].c+=f;
ret+=f;
}
}
return ret;
}
void dinic()
{
while (bfs()) ans-=dfs(s,inf);
}
int main()
{
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
{
for (j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
ans+=a[i][j];
}
}
s=0; t=n*m+1;
for (i=1;i<=n;i++)
{
for (j=1;j<=m;j++)
{
if ((i+j)%2)
{
add(s,fc(i,j),a[i][j]);
if (i>1) add(fc(i,j),fc(i-1,j),inf);
if (i<n) add(fc(i,j),fc(i+1,j),inf);
if (j>1) add(fc(i,j),fc(i,j-1),inf);
if (j<m) add(fc(i,j),fc(i,j+1),inf);
}
else add(fc(i,j),t,a[i][j]);
}
}
dinic();
printf("%d",ans);
}