1475: 方格取数
Time Limit: 5 Sec
Memory Limit: 64 MB
Submit: 797
Solved: 403
Description
在一个n*n的方格里,每个格子里都有一个正整数。从中取出若干数,使得任意两个取出的数所在格子没有公共边,且取出的数的总和尽量大。
Input
第一行一个数n;(n<=30) 接下来n行每行n个数描述一个方阵
Output
仅一个数,即最大和
Sample Input
2
1 2
3 5
1 2
3 5
Sample Output
6
题解:
题目显然在要求一个最大点权独立集,最大流=最小割=sum-最大点权独立集:
为什么最小割等于最大点权独立集呢?
题目中说不可以选相邻的两个数,这样我们可以将方格拆成一个二分图;
对于点(i,j):
如果(i+j)为奇数,我们就将其向源点连边;
如果(i+j)为偶数,我们就将其向汇点连边;
我们可以把不能在同一个集合里的点之间连边(也就是上述的两类点),边权设置为无穷大;
然后求出最小割。
最小割的意义是:我们每割掉一条边也就意味着我们不选这个点了,直到图中没有流可以经过时我们就相当于满足了题目中的:不可以取相邻的两个数。
因为是最小的割,所以最大点权独立集也就是“所有点点权之和”-“最小割”,为了求出最小割我们跑最大流就可以了
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=35;
const int M=10005;
const int inf=2100000000;
int x,n,sum,mp[N][N],S,T,ans;
int to[M],nxt[M],lj[N*N],w[M],cnt=-1;
void add(int f,int t,int p)
{
cnt++;
to[cnt]=t;
nxt[cnt]=lj[f];
lj[f]=cnt;
w[cnt]=p;
cnt++;
to[cnt]=f;
nxt[cnt]=lj[t];
lj[t]=cnt;
w[cnt]=0;
}
queue<int>Q;
int d[N*N];
bool bfs()
{
memset(d,0,sizeof(d));
d[0]=1;
Q.push(0);
while(!Q.empty())
{
int x=Q.front();
Q.pop();
for(int i=lj[x];i>=0;i=nxt[i])
if(w[i]&&!d[to[i]])
{
d[to[i]]=d[x]+1;
Q.push(to[i]);
}
}
if(d[T]) return true;
return false;
}
int dfs(int x,int v)
{
if(x==T||v==0) return v;
int ret=0;
for(int i=lj[x];i>=0;i=nxt[i])
if(d[to[i]]==d[x]+1)
{
int f=dfs(to[i],min(w[i],v));
w[i]-=f;
w[i^1]+=f;
v-=f;
ret+=f;
if(v==0) break;
}
return ret;
}
int get(int x,int y)
{return (x-1)*n+y;}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
scanf("%d",&mp[i][j]);
sum+=mp[i][j];
}
S=0,T=n*n+1;
for(int i=0;i<=T;i++) lj[i]=-1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if((i+j)%2==0)
{
add(S,get(i,j),mp[i][j]);
if(i>=2) add(get(i,j),get(i-1,j),inf);
if(j>=2) add(get(i,j),get(i,j-1),inf);
}
if((i+j)%2==1)
{
add(get(i,j),T,mp[i][j]);
if(i>=2) add(get(i-1,j),get(i,j),inf);
if(j>=2) add(get(i,j-1),get(i,j),inf);
}
}
while(bfs()) ans+=dfs(0,inf);
printf("%d",sum-ans);
}