洛谷 P1252 最小费用最大流

https://www.luogu.org/problemnew/show/P1251

题目描述

一个餐厅在相继的 NNN 天里,每天需用的餐巾数不尽相同。假设第 iii 天需要 rir_iri​块餐巾( i=1,2,...,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 ppp 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 nnn 天(n>mn>mn>m),其费用为 sss 分(s<fs<fs<f)。

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

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

输入输出格式

输入格式:

 

由标准输入提供输入数据。文件第 1 行有 1 个正整数 NNN,代表要安排餐巾使用计划的天数。

接下来的 NNN 行是餐厅在相继的 NNN 天里,每天需用的餐巾数。

最后一行包含5个正整数p,m,f,n,sp,m,f,n,sp,m,f,n,s。ppp 是每块新餐巾的费用; mmm 是快洗部洗一块餐巾需用天数; fff 是快洗部洗一块餐巾需要的费用; nnn 是慢洗部洗一块餐巾需用天数; sss 是慢洗部洗一块餐巾需要的费用。

 

输出格式:

 

将餐厅在相继的 N 天里使用餐巾的最小总花费输出

 

输入输出样例

输入样例#1: 复制

3
1 7 5 
11 2 2 3 1

输出样例#1: 复制

134

说明

N<=2000

ri<=10000000

p,f,s<=10000

时限4s

思路:

首先,我们拆点,将一天拆成晚上和早上,每天晚上会受到脏餐巾(来源:当天早上用完的餐巾,在这道题中可理解为从原点获得),每天早上又有干净的餐巾(来源:购买、快洗店、慢洗店)。

1.从原点向每一天晚上连一条流量为当天所用餐巾x,费用为0的边,表示每天晚上从起点获得x条脏餐巾。

2.从每一天早上向汇点连一条流量为当天所用餐巾x,费用为0的边,每天白天,表示向汇点提供x条干净的餐巾,流满时表示第i天的餐巾够用 。 3.从每一天晚上向第二天晚上连一条流量为INF,费用为0的边,表示每天晚上可以将脏餐巾留到第二天晚上(注意不是早上,因为脏餐巾在早上不可以使用)。

4.从每一天晚上向这一天+快洗所用天数t1的那一天早上连一条流量为INF,费用为快洗所用钱数的边,表示每天晚上可以送去快洗部,在地i+t1天早上收到餐巾 。

5.同理,从每一天晚上向这一天+慢洗所用天数t2的那一天早上连一条流量为INF,费用为慢洗所用钱数的边,表示每天晚上可以送去慢洗部,在地i+t2天早上收到餐巾 。

6.从起点向每一天早上连一条流量为INF,费用为购买餐巾所用钱数的边,表示每天早上可以购买餐巾 。 注意,以上6点需要建反向边!3~6点需要做判断(即连向的边必须<=n)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;

const ll INF=1e16;
const int maxn=5005;
const int maxm=50005;

struct Edge
{
	int to,nxt;
	ll f,w;
}edge[maxm<<1];

int n,m,s,t,tot;
int head[maxn];
int prevx[maxn],pree[maxn],inque[maxn];
ll dist[maxn];
queue<int> que;

inline void addedge(int u,int v,ll c,ll w)//容量为c 权值为w
{
	edge[++tot].to=v,edge[tot].f=c,edge[tot].w=w,edge[tot].nxt=head[u],head[u]=tot;
	edge[++tot].to=u,edge[tot].f=0,edge[tot].w=-w,edge[tot].nxt=head[v],head[v]=tot;
}

bool findpath()
{
	while(!que.empty())
		que.pop();
	que.push(s);
	fill(dist,dist+t+1,INF);
	dist[s]=0;
	inque[s]=1;
	while(!que.empty())
	{
		int u=que.front();
		que.pop();
		for(int i=head[u];i;i=edge[i].nxt)
		{
			if(edge[i].f>0&&dist[u]+edge[i].w<dist[edge[i].to])
			{
				dist[edge[i].to]=dist[u]+edge[i].w;
				prevx[edge[i].to]=u;
				pree[edge[i].to]=i;
				if(!inque[edge[i].to])
				{
					inque[edge[i].to]=1;
					que.push(edge[i].to);
				}
			}
		}
		inque[u]=0;
	}
	if(dist[t]<INF)
		return 1;
	return 0;
}

void mincostfolw()
{
	ll ans=0;
	while(findpath())
	{
		int u=t;
		ll delta=INF;
		while(u!=s)
		{
			if(edge[pree[u]].f<delta)
				delta=edge[pree[u]].f;
			u=prevx[u];
		}
		u=t;
		while(u!=s)
		{
			edge[pree[u]].f-=delta;
			edge[pree[u]^1].f+=delta;
			u=prevx[u];
		}
		ans+=dist[t]*delta;
	}
	printf("%lld\n",ans);
}

int main()
{
	tot=1;
	scanf("%d",&n);
	s=0,t=2*n+1;
	int c;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&c);
		addedge(s,i,c,0);
		addedge(i+n,t,c,0);
	}
	int p,m,f,n1,s1;
	scanf("%d%d%d%d%d",&p,&m,&f,&n1,&s1);
	for(int i=1;i<=n;i++)
	{
		addedge(s,i+n,INF,p);
		if(i+1<=n)
			addedge(i,i+1,INF,0);
		if(i+m<=n)
			addedge(i,i+m+n,INF,f);
		if(i+n1<=n)
			addedge(i,i+n1+n,INF,s1);
	}
	mincostfolw();
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值