Problem Description
给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
Input
包括多个测试实例,每个测试实例包括一个整数n 和n*n个非负数(n<=20)
Output
对于每个测试实例,输出可能取得的最大的和
Sample Input
3 75 15 21 75 15 28 34 70 5
Sample Output
188
两种做法 :
点覆盖集:无向图G的一个点集,使得该图中所有边都至少有一个端点在该集合内。
// 最小点权覆盖集:在带点权无向图G中,点权之和最小的覆盖集。
// 点独立集:无向图G的一个点集,使得任两个在该集合中的点在原图中都不相邻。
// 最大点权独立集:在带权无向图G中,点权之和最大的独立集。
// 定理:
// 1. 最小点权覆盖集=最小割=最大流
// 2. 最大点权独立集=总权-最小点权覆盖集
1 . 对于整个图,把坐标和(x+y)是偶数的点放在一堆,坐标和是奇数的放一堆;因为整个图,坐标和为偶数的任意两点都不是相邻的,坐标和为奇数的也一样;
我们把坐标和为偶数的点连源点,容量为数值本身;坐标和为奇数的连汇点,容量为数值本身;
对于每个点,于它上下左右四个点连边,这四个点都是和它相邻的,所以不能喝它共存;容量为inf ;
得到建好的网络后,我们就可以就对网络求最小割,因为被割的边和最小,题目求最大,所以结果是 sum-sap();
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
const int N=450 ;
const int M=N*8 ;
const int inf = 1<<30;
struct node
{
int u,v,c,next;
}edge[M];
int pre[N],head[N],dis[N],cur[N],gap[N];
int top ;
int move[4][2]={0, 1, 0, -1, 1, 0, -1, 0};
void add(int u ,int v,int c)
{
edge[top].u=u;
edge[top].v=v;
edge[top].c=c;
edge[top].next=head[u];
head[u]=top++;
edge[top].u=v;
edge[top].v=u;
edge[top].c=0;
edge[top].next=head[v];
head[v]=top++;
}
int sap(int s,int t,int n)
{
int u,v,mindis,minflow=inf,flow=0;
memset(gap,0,sizeof(gap));
memset(dis,0,sizeof(dis));
memset(pre,-1,sizeof(pre));
for(int i=0;i<n;i++) cur[i]=head[i] ;
u=pre[s]=s;
gap[0]=n;
while(dis[s] <n)
{
loop:
for(int &i=cur[u] ; i!=-1 ; i=edge[i].next)
{
v=edge[i].v;
if(edge[i].c && dis[u]==dis[v]+1)
{
pre[v]=u;
u=v;
if(minflow > edge[i].c)
minflow = edge[i].c ;
if(v==t)
{
for(u=pre[v] ; v!=s ; v=u,u=pre[u])
{
edge[cur[u]].c -=minflow ;
edge[cur[u]^1].c += minflow ;
}
flow +=minflow ;
minflow=inf;
}
goto loop ;
}
}
mindis=n;
for(int i=head[u];i!=-1 ; i=edge[i].next)
{
v=edge[i].v;
if(edge[i].c>0 && dis[v] < mindis)
{
mindis=dis[v];
cur[u]=i;
}
}
if((--gap[dis[u]]) ==0) break;
gap[dis[u]=mindis+1]++;
u=pre[u];
}
return flow ;
}
int main()
{
int n ,x ,sum;
while(~scanf("%d",&n))
{
top=0;sum=0;
memset(head,-1,sizeof(head));
int s=0,t=n*n+1 ;
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= n ; j++)
{
int tmp=(i-1)*n+j ;
scanf("%d",&x);
if((i+j)%2==0) //坐标和为偶数
add(s,tmp,x);
else
add(tmp,t,x) ;
sum+=x;
}
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= n ; j++)
if((i+j)%2==0) //坐标和为偶数的点跟周围的点建边就可以把所有点都建完了;
{
int tmp=(i-1)*n+j;
if(i>1) add(tmp,tmp-n,inf) ; //判断边界;
if(i<n) add(tmp,tmp+n,inf) ;
if(j<n) add(tmp,tmp+1,inf) ;
if(j>1) add(tmp,tmp-1,inf) ;
}
int ans=sap(s,t,t+1);
printf("%d\n",sum-ans) ;
}
return 0;
}