BZOJ 1324: Exca王者之剑/BZOJ 1475: 方格取数 最大权独立集 最小割

1324: Exca王者之剑

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 618  Solved: 310
[Submit][Status][Discuss]

Description

 

Input

第一行给出数字N,M代表行列数.N,M均小于等于100 下面N行M列用于描述数字矩阵

Output

输出最多可以拿到多少块宝石

Sample Input

2 2
1 2
2 1

Sample Output

4

改一下方格尺寸这个题就和方格取数一样了

那么我们来写题解
旨在讲清楚最大点权独立集(最小点权覆盖)的一般解法

题目显然在要求一个最大点权独立集(概念请见传送门
最大点权独立集与最小点权覆盖是对偶问题,这里先介绍最小点权覆盖的解法。

最小点权覆盖问题是指:
给出一张二分图,二分图的每个节点带有一个点权,要求从中选出若干节点
使得这些节点能够覆盖二分图中所有的边,并使得节点的权值和最小。
该类问题可用网络流最小割算法来解决。
考虑最小割的性质,最小割能够将原图中所有的点划分为两个集合,这能够与最小点权覆盖问题中的点得选中与否对应
如果能够找到一种建模方式
满足在二分图中的每条边连接的两个点中,至少一个被选中,就能使网络流的最小割与二分图的最小点权覆盖相匹配。

于是考虑如下建模方式:
建立源点S,汇点T,保留二分图中的所有节点xi,yi,从S到所有xi连边,权值为xi的权值;
若二分图中xi到yi有边,在网络流图中从xi向yi连边,权值为无穷;从所有yi到T连边,权值为yi的权值。

这样,二分图中的每条边连接的两个点中,要么与S形成割边,要么与T形成割边(因为这两个点之间的边权为inf)
这就保证了两个点中,至少一个被选中,因此最小割就对应了一个最小点权的选点方案。
故网络流图中的最小割就是原问题的最小点权。

而最大点权独立集问题中,需要满足在二分图中的每条边连接的两个点中,选择至多一个,最终是选择的点的点权和最大
换句话说,就是在二分图中的每条边连接的两个点中,至少不选择一个,使得不选择的点的点权和最小。
说到这里,大家应该能看出最大点权独立集合最小点权覆盖问题的联系了:
最大点权独立集=权值和-最小点权覆盖

在了解了解法之后就当然会做啦
我们可以将方格拆成一个二分图;
对于点(i,j):
如果(i+j)为奇数,我们就将其向源点连边;
如果(i+j)为偶数,我们就将其向汇点连边;
我们可以把不能在同一个集合里的点之间连边(也就是上述的两类点),边权设置为无穷大;
然后求出最小割。


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<queue>
#include<set>
#include<map>
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return f*x;
}
const int N=10010;const int inf=0X7f7f7f7f;
int n,m,mp[110][110],ecnt=1,last[N],d[N],q[N],ans,S,T=N-2;
struct EDGE{int to,nt,val;}e[N<<5];
inline void readd(int u,int v,int val)
{e[++ecnt]=(EDGE){v,last[u],val};last[u]=ecnt;}
inline void add(int u,int v,int val)
{readd(u,v,val);readd(v,u,0);}
bool bfs()
{
	memset(d,0,sizeof(d));
	d[S]=1;int head=0,tail=1;q[0]=S;
	while(head<tail)
	{
		int u=q[head++];
		for(int i=last[u];i;i=e[i].nt)
		if(e[i].val&&!d[e[i].to])
		{
			d[e[i].to]=d[u]+1;
			q[tail++]=e[i].to;
		}
	}
	return d[T];
}
int dfs(int u,int lim)
{
	if(u==T||!lim)return lim;
	int flow=0;
	for(int i=last[u];i;i=e[i].nt)
	if(d[e[i].to]==d[u]+1)
	{
		int tmp=dfs(e[i].to,min(e[i].val,lim));
		flow+=tmp;lim-=tmp;e[i].val-=tmp;e[i^1].val+=tmp;
		if(!lim)break;
	}
	if(!flow)d[u]=-1;
	return flow;
}
void dinic()
{while(bfs()){ans+=dfs(S,inf);}}
int main()
{
	n=read();m=read();int sum=0;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
	{
		mp[i][j]=read();sum+=mp[i][j];
		if((i+j)%2)
		{
			add(S,(i-1)*m+j,mp[i][j]);
			if(j>1)add((i-1)*m+j,(i-1)*m+j-1,inf);
			if(i>1)add((i-1)*m+j,(i-2)*m+j,inf);
		}
		else
		{
			add((i-1)*m+j,T,mp[i][j]);
			if(j>1)add((i-1)*m+j-1,(i-1)*m+j,inf);
			if(i>1)add((i-2)*m+j,(i-1)*m+j,inf);
		}
	}
	dinic();
	printf("%d\n",sum-ans);
	return 0;
} 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值