洛谷P1251:餐巾计划问题

问题描述

      一个餐厅在相继的 N 天里,每天需用的餐巾数不尽相同。假设第 i 天需要 ri 块餐巾( i=1,2,...,N)。

      餐厅可以购买新的餐巾,每块餐巾的费用为 p 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 n 天( n>m ),其费用为 s 分(s<f )。

      每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。

      试设计一个算法为餐厅合理地安排好 N 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。

思路与解法

      我们这样想一下,毕竟是用网络流来做,又因为新餐巾总数*清洗或购买毛巾单价费用=所用钱数,所以很明显满足流量*费用=所用钱数。既然满足这个式子,我们就想到要用费用流来做。

      费用流有两种,第一种是最小费用最大流,第二种是有上下界的费用流。第二种还没学,所以做法今后再讲。那么对于第一种,我们应该怎么构图呢?

      1.首先,早上的获取餐布的途径是不是有三种,快洗后的,慢洗后的,和重新购买的。(有同学会说,上一天留下来的呢?不用考虑,因为脏抹布也可以留到第二天洗)。

      2.而脏抹布的去处呢?也有三种,留着,送去快洗店,送去慢洗店。

      所以,我们进行拆点,把点i拆成i和i’,前者表示晚上,而后者表示早上。

      *1.所以我们从begin(源点)到每一天的晚上建一条流量为ri,费用为0的边。表示当天晚上有ri条脏餐布。也从i’到end连一条流量为ri,费用为0的边,当这个流完时,表示第i天早上的餐布够用。

      *2.根据上面所述,晚上的餐布去处有三种快洗,慢洗,和留到下一天,对应的便是,往快洗后的那一天早上连一条流量为INF,费用为单价的边,表示可以洗无数的餐布,洗每条餐布的费用是单价。往慢洗后的那一天早上连一条流量为INF,费用为单价的边,表示可以洗无数的餐布,洗每条餐布的费用是单价。和往i+1连一条INF,费用为0的边。

      *3.还差一个东西,那就是,连一条从begin到i’(1<=i<=n)的流量为INF,费用为购买单价的边。

      完了。

代码如下,注意开long long。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define INF 1e9

struct edge{int x,y,next,c,cos;};
edge s[300010];
int first[4010];
int n;
int time1,time2,money1,money2,p;
int begin=0,end;
int f[4010];
int st,ed;
bool tf[4010];
int len=1;
long long mmin[4010];
int ip[4010];
long long h[4010];

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].c=c;s[len].cos=cos;s[len].next=first[x];
	first[x]=len;
	len++;
	s[len].x=y;s[len].y=x;s[len].c=0;s[len].cos=-cos;s[len].next=first[y];
	first[y]=len;
}

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

int main()
{
	scanf("%d",&n);
	end=2*n+1;
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		ins(begin,i,x,0);
		ins(i+n,end,x,0);
	}
	scanf("%d %d %d %d %d",&p,&time1,&money1,&time2,&money2);
	for(int i=1;i<=n;i++)
	{
		if(i+1<=n) ins(i,i+1,INF,0);
		if(i+time1<=n) ins(i,i+time1+n,INF,money1);
		if(i+time2<=n) ins(i,i+time2+n,INF,money2);
		ins(begin,i+n,INF,p);
	}
	long long cost=0,flow=0;
	while(SPFA(cost,flow));
	printf("%lld\n",cost);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值