bzoj-2127 happiness

141 篇文章 0 订阅
44 篇文章 0 订阅

题意:

给出一个nxm的座位表,每个同学学文或学理有一个喜悦度;

并且如果两个挨着的同学一起学文或学理,会再有一个喜悦度加成;

喜悦度都为非负整数,n,m<=100,答案在int内;


题解:
文理分科,似乎是网络流的经典问题呢;

因为让所有人都幸福的世界是不可能存在的,所以我们就采用最小割的思想;

先将所有的喜悦度都加起来,然后将割掉一些使其合法,当然要满足割最小;

这里的建图还是去膜了黄学长,然后得到建图如下;

对于原图中所有相邻的两个人A,B,我们建边:

s->A:cost[A文]+c[文][A][B]/2,s->B:cost[B文]+c[文][A][B]/2;

A->t:cost[A理]+c[理][A][B]/2,B->t:costB[理]+c[理][A][B]/2;

A<–>B:c[文][A][B]/2+c[理][A][B]/2

黄学长的建图非常有道理,具体来说就是这样的一个图 (我们暂且考虑两个点):


建图之后,显然这个网络只有这四种割是可能最小的;

而这又恰好代表了四种不同的方案,引申到更多点也是同理的;

所以得到原图的最小割之后,用总的喜悦度减去就可以了;

为了避免精度误差将所有喜悦度翻倍之后再计算;

注意那个双向边要建真正的双向而不是两条单向,因为两条单向的复杂度会变得很奇怪。。。


代码:


#include<queue>
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110
#define M 1210000
using namespace std;
int c[6][N][N],num[N][N];
int next[M],to[M],flow[M],head[N*N],ce=1;
int S,T,dis[N*N];
queue<int>q;
void add(int x,int y,int fl)
{
	if(!x||!y)	return ;
	to[++ce]=y;
	flow[ce]=fl;
	next[ce]=head[x];
	head[x]=ce;
	to[++ce]=x;
	flow[ce]=0;
	next[ce]=head[y];
	head[y]=ce;
}
void add2(int x,int y,int fl)
{
	if(!x||!y)	return ;
	to[++ce]=y;
	flow[ce]=fl;
	next[ce]=head[x];
	head[x]=ce;
	to[++ce]=x;
	flow[ce]=fl;
	next[ce]=head[y];
	head[y]=ce;
}
bool BFS()
{
	memset(dis,0,sizeof(dis));
	dis[S]=1;
	q.push(S);
	int i,x;
	while(!q.empty())
	{
		x=q.front(),q.pop();
		for(i=head[x];i;i=next[i])
		{
			if(flow[i]&&!dis[to[i]])
			{
				dis[to[i]]=dis[x]+1;
				q.push(to[i]);
			}
		}
	}
	return dis[T]!=0;
}
int dfs(int x,int fl)
{
	if(x==T)	return fl;
	int i,ret,temp;
	for(i=head[x],ret=0;i;i=next[i])
	{
		if(flow[i]&&dis[to[i]]==dis[x]+1)
		{
			temp=dfs(to[i],min(fl-ret,flow[i]));
			ret+=temp;
			flow[i]-=temp,flow[i^1]+=temp;
			if(ret==fl)
				return ret;
		}
	}
	return ret;
}
int main()
{
	int n,m,i,j,k,x,y,ans,sum;
	scanf("%d%d",&n,&m);
	sum=0;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			scanf("%d",&c[0][i][j]),sum+=c[0][i][j];
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			scanf("%d",&c[1][i][j]),sum+=c[1][i][j];
	for(i=1;i<n;i++)
		for(j=1;j<=m;j++)
			scanf("%d",&c[2][i][j]),sum+=c[2][i][j];
	for(i=1;i<n;i++)
		for(j=1;j<=m;j++)
			scanf("%d",&c[3][i][j]),sum+=c[3][i][j];
	for(i=1;i<=n;i++)
		for(j=1;j<m;j++)
			scanf("%d",&c[4][i][j]),sum+=c[4][i][j];
	for(i=1;i<=n;i++)
		for(j=1;j<m;j++)
			scanf("%d",&c[5][i][j]),sum+=c[5][i][j];
	for(i=1,k=0;i<=n;i++)
		for(j=1;j<=m;j++)
			num[i][j]=++k;
	S=++k,T=++k,sum<<=1;
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			x=num[i][j];
			add(S,x,(c[0][i][j]<<1)+c[2][i-1][j]+c[2][i][j]+c[4][i][j-1]+c[4][i][j]);
			add(x,T,(c[1][i][j]<<1)+c[3][i-1][j]+c[3][i][j]+c[5][i][j-1]+c[5][i][j]);
			add2(x,num[i+1][j],c[2][i][j]+c[3][i][j]);
			add2(x,num[i][j+1],c[4][i][j]+c[5][i][j]);
		}
	}
	ans=0;
	while(BFS())
		ans+=dfs(S,0x3f3f3f3f);
	sum-=ans;
	sum>>=1;
	printf("%d\n",sum);
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值