洛谷P4016:负载平衡问题

问题描述

      有一个由n个沿环形铁路分布的仓库,每个仓库有一定的货物,某一个仓库可以往两边的仓库运送货物,求使n个仓库货物相等时的最小运输量。


怎么做?

      这道题有很多做法,有贪心的,有二分的,有网络流的,其他的算法相信同学们可以在其他的题解上看到,所以在这里主要讲一下网络流的做法。其实这是一道最小费用最大流的很好的题。

解法

      我们以一批货物从原始仓库到目标仓库所经过的仓库个数作为费用,以这批货物的大小作为流量,那么这时的运输量就是费用*流量。没错,如果想出了这个公式,我们就可以很快地解出这道题。

      1.我们先拆点,因为如果只有一个点会很难建图。现在对于仓库i有i和i’两个点,i为贡献点,i’为需求点。

      2.如果数量-平均值<0,那么这个点只可能是需求点,所以i’到t连一条流量为平均值-数量,费用为0。

      3.如果数量-平均值>0,那么这个点只可能是贡献点,所以s到i连一条流量为数量-平均值,费用为0。

      4.中间的点就i的贡献点到左右的需求点连一条流量为无限,费用为1的边,因为这批货物在这里出现新一次的转移。

      5.有很多同学想问,你这样满足的只是两个相邻点之间的货物转移,不能满足跨点运输。对!所以我们在贡献点之间连一条流量为无限,费用为1的边,因为在这里,货物进行新一次的转移。

最后跑一遍最小费用最大流,把流量*费用的总和输出就行。

这里的最大流保证了每个仓库都满足要求,最小费用则保证了运输量最小。

贴代码,我的代码风可能会引起不适。

#include<cstdio>
#include<cstdlib>
#include<cstring>

struct edge{int x,y,next,c,cos;};
edge s[1010];
int n;
int p[110];
int tot=0;
int len=1;
int f[1010];
int st=1,ed=2;
int sum[1010],mmin[1010],ip[1010];
int first[1010];
int begin=0,end;
bool tf[1010];

int min(int x,int y)
{
	return x<y?x:y;
}

void ins(int x,int y,int c,int cos)
{
	len++;
	s[len].x=x;s[len].y=y;s[len].next=first[x];first[x]=len;s[len].cos=cos;s[len].c=c;
	len++;
	s[len].x=y;s[len].y=x;s[len].next=first[y];first[y]=len;s[len].cos=-cos;s[len].c=0;
}

int SPFA(int &cost,int &flow)
{
	memset(sum,63,sizeof(sum));
	memset(mmin,63,sizeof(mmin));
	memset(tf,false,sizeof(tf));
	st=1,ed=2;
	tf[begin]=true;
	sum[begin]=0;
	f[st]=begin;
	while(st!=ed)
	{
		int x=f[st];
		tf[x]=false;
		for(int i=first[x];i!=0;i=s[i].next)
		{
			int y=s[i].y;
			if(sum[y]>sum[x]+s[i].cos && s[i].c>0)
			{
				ip[y]=i;
				mmin[y]=min(mmin[x],s[i].c);
				sum[y]=sum[x]+s[i].cos;
				if(tf[y]==false)
				{
					tf[y]=true;
					f[ed]=y;	
					ed++;
				}
			}
		}
		st++;
	}
	if(sum[end]==1061109567) return 0;
	flow+=mmin[end];
	cost+=mmin[end]*sum[end];
	int u=end;
	while(u!=begin)
	{
		int i=ip[u];
		s[i].c-=mmin[end];
		s[i^1].c+=mmin[end];
		u=s[i].x;
	}
	return 1;
}

int cost_flow()
{
	int cost=0,flow=0;
	while(SPFA(cost,flow));
	return cost;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&p[i]);
		tot+=p[i];
	}
	tot/=n;
	end=2*n+1;
	for(int i=1;i<=n;i++)
	{
		p[i]-=tot;
		if(p[i]<0)
			ins(i+n,end,-p[i],0);
		if(p[i]>0)
			ins(begin,i,p[i],0);
		if(i-1>=1) 
		{
			ins(i,i-1,1e9,1);
			ins(i,i-1+n,1e9,1);
		}
		else
		{
			ins(i,n+n,1e9,1);
			ins(i,n,1e9,1);
		}
		if(i+1<=n) 
		{
			ins(i,i+1,1e9,1);
			ins(i,i+1+n,1e9,1);
		}
		else 
		{
			ins(i,n+1,1e9,1);
			ins(i,1,1e9,1);
		}
	}
	int ans=cost_flow();
	printf("%d\n",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值