BZOJ1475: 方格取数 网络流+最大点权独立集

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

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);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值