BZOJ3774 最优选择(网络流最小割二元关系)

124 篇文章 0 订阅
92 篇文章 0 订阅

在这里插入图片描述
对于两个01变量,如果我们把图造成这个样子:
在这里插入图片描述
a为x为0的代价,c为x为1的代价。
b,d同理于y
那么x,y之间好像没有关系。
在这里插入图片描述
那么如果最小花费为最小割,可以列出4个关于abcdef的方程使得图中最小割为最小花费。
在这里插入图片描述
然后我们就可以用最大流(最小割)初步处理二元关系了。在这里插入图片描述
这时2e=v1+v2-v3-v4=-K
这里说的是如果K = v3 + v4 - v1 - v2(相异减相同)小于0,那么e会小于0,无法建图,这时我们将一个点对源汇的连边交换,就可以得到e=-K/2,大于等于0,可以建图。
如果二元关系构成了一个二分图,那么我们可以将一部中的所有点的对源汇的连边交换来处理K小于0的情况(比如说此题)。

那么我们来试着建模:
x与x1(表示四周是否被占):
在这里插入图片描述
注意中间的边是两条有向边,因为情况不能统一。
我们可以通过想来算出这个图:
我们先将答案加上所有的B的两倍,表示每个点被占和四周被占,之后再来删。
上图对于x,左边是不被占,右边是被占,对于x1,左边是四周被占,右边是四周不被占。(x和x1相异小于x与x1相同)
那么x选左就是B,选右就是A(记住我们现在是算B的两倍减去最小割,这个权值代表代价)
x1也选左就是0,x和x1一起选右就是A+B,x选右,x1选左就是A+B,x选左,x1选右就是B+B,完了。。。
然后x1与四周就是x1有,四周有1个没有,费用为inf,这个相异为inf,不用转,这虽然不是一个二分图,但是我们都把图都建出来了就算了吧。(x1可以不要就是一个二分图,但这样就不好套了)

AC Code:

#include<bits/stdc++.h>
#define maxn 2*55*55
#define inf 0x3f3f3f3f
#define maxm maxn * 100
using namespace std;

int n,m;
int buf[maxn],info[maxn],Prev[maxm],to[maxm],cap[maxm],cnt_e=1;
inline void Node(int u,int v,int c){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cap[cnt_e]=c; }
inline void Line(int u,int v,int c){ Node(u,v,c),Node(v,u,0);}
int mark[55][55],a[55][55],b[55][55];
int ans = 0;
int S , T , tot;
int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}},dis[maxn];

int aug(int now,int Max)
{
	if(now == T) return Max;
	int inc , st = Max;
	for(int i=info[now];i && st;i=Prev[i])
		if(cap[i] && dis[to[i]] + 1 == dis[now])
		{
			inc = aug(to[i] , min(cap[i] , st));
			if(inc) st -= inc , cap[i] -= inc , cap[i^1] += inc;
			else dis[to[i]] = 0;
			if(!st) break;
		}
	return Max - st;
}

bool BFS()
{
	static queue<int>q;
	memset(dis,-1,sizeof dis);
	dis[T] = 0 , q.push(T);
	for(int now;!q.empty();)
	{
		now = q.front() , q.pop();
		for(int i=info[now];i;i=Prev[i])
			if(cap[i^1] && dis[to[i]] == -1)
			{
				dis[to[i]] = dis[now] + 1;
				q.push(to[i]);
			}
	}
	return dis[S] != -1;
}

int main()
{
	
	//freopen("1.in","r",stdin);
	scanf("%d%d",&n,&m);
	S = n * m + 1 , T = n * m + 2;
	tot = T;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			mark[i][j] = (i-1) * m + j;
			if((i+j) & 1) Line(mark[i][j],T,a[i][j]);
			else	 Line(S,mark[i][j],a[i][j]);
		}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&b[i][j]);
			if(!((i+j) & 1)) Line(mark[i][j],T,b[i][j]);
			else Line(S,mark[i][j],b[i][j]);
			ans += b[i][j] * 2;
			
			++tot;
			if((i+j) & 1) Line(tot,T,b[i][j]) , Line(mark[i][j],tot,b[i][j]);
			else Line(S,tot,b[i][j]) , Line(tot,mark[i][j],b[i][j]);
				
			for(int d=0,x,y;d<4;d++)
				if((x=i+dir[d][0])>=1 && x<=n && (y=j+dir[d][1])>=1 && y<=m)
					if((i+j) & 1) Line(mark[x][y],tot,inf);
					else Line(tot,mark[x][y],inf);
		}
	memcpy(buf,info,sizeof info);
	for(;BFS();)
		ans -= aug(S,inf),
		memcpy(info,buf,sizeof buf);
	printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值