「网络流 24 题」餐巾计划 最小费用最大流

「网络流 24 题」餐巾计划

神题!!

可以把餐巾分为两类,一类是新餐巾(买来的或是洗过的),一类是旧餐巾(用过的)。

发现每一天需要A[i]条新餐巾,也就是会产生A[i]条旧餐巾。于是我们把每一天建两个点,一个相当于存放旧餐巾的站点(存放当天的使用产生的旧餐巾),一个相当于存放新餐巾的站点,流就相当于餐巾的转移。

然后就可以把源点看成卖餐巾的点,向每天的新餐巾存放点连一条流量无穷费用为购买费用的边。那么汇点就可以看作是这个餐厅,需要每天把一定数量的新餐巾给他。于是就从每天的新餐巾存放点向汇点连一条流量为当天需要的餐巾数量,费用为0的边。

由于每天的新餐巾还可以由之前的旧餐巾洗了之后而来,故从那个正好在那天洗可以在今天用到的旧餐巾站点向今天的新餐巾连一个流量无穷,费用为洗餐巾的费用的边(快洗和慢洗都一样)。

同时,我们可以看做每天的新餐巾给餐厅(汇点)用完之后又给了商店,于是从源点向每天旧餐巾的点连一条流量为当天餐巾的需求量,费用为0的边。

还有由于每天没用完的新餐巾可以留到下一天用,于是每天的新餐巾存放点向下一天的新餐巾存放点连一条流量无穷费用为0的边。(旧餐巾就不用留了,他们总是要洗,故洗了当做新餐巾再留和先留再被洗是等效的)。

之后跑一遍最小费用最大流就是了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define INF 1000000000
#define BIGINF 1000000000000000000ll
#define M 2005
using namespace std;
struct E{
	int to,nx,val,cost;
}edge[M*13];
int tot,head[M*2];
void check_min(int &x,int y){if(x>y)x=y;}
void Addedge(int a,int b,int d,int cost){
	edge[++tot].to=b;
	edge[tot].val=d;
	edge[tot].cost=cost;
	edge[tot].nx=head[a];
	head[a]=tot;
}
int A[M];
int n,cost,tim_f,cost_f,tim_l,cost_l,s,t;
int clr[M],dir[M];
struct Pre_data{
	int p_id,e_id;
}Pre[M*2];
long long dis[M*2];
bool vis[M*2];
bool SPFA(){
	memset(dis,127,sizeof(dis));
	dis[s]=0;
	queue<int>Q;
	Q.push(s);
	while(!Q.empty()){
		int now=Q.front();Q.pop();
		vis[now]=false;
		for(int i=head[now];~i;i=edge[i].nx){
			int nxt=edge[i].to;
			if(!edge[i].val)continue;
			if(dis[now]+edge[i].cost<dis[nxt]){
				dis[nxt]=dis[now]+edge[i].cost;
				Pre[nxt]=(Pre_data){now,i};
				if(!vis[nxt]){Q.push(nxt);vis[nxt]=true;}
			}
		}
	}
	return dis[t]<BIGINF;
}
long long network_flow(){
	long long tot_cost=0;
	int res=0;
	while(SPFA()){
		int now=t;
		int mn=1e9;
		while(now!=s){
			check_min(mn,edge[Pre[now].e_id].val);
			now=Pre[now].p_id;
		}
		res+=mn;
		now=t;
		while(now!=s){
			if(abs(edge[Pre[now].e_id].cost)!=INF)tot_cost+=1ll*mn*edge[Pre[now].e_id].cost;
			edge[Pre[now].e_id].val-=mn;
			edge[Pre[now].e_id^1].val+=mn;
			now=Pre[now].p_id;
		}
	}
	return tot_cost;
}
int main(){
	tot=-1;memset(head,-1,sizeof(head));
	scanf("%d%d%d%d%d%d",&n,&cost,&tim_f,&cost_f,&tim_l,&cost_l);
	for(int i=1;i<=n;i++)scanf("%d",&A[i]);
	for(int i=1;i<=n;i++){clr[i]=i;dir[i]=i+n;}//clr每天新餐巾存放点 dir每天旧餐巾存放点 
	s=n*2+1,t=n*2+2;
	for(int i=1;i<=n;i++){
		Addedge(s,clr[i],INF,cost);Addedge(clr[i],s,0,-cost);//各种连边 
		Addedge(clr[i],t,A[i],0);Addedge(t,clr[i],0,0);
		Addedge(s,dir[i],A[i],0);Addedge(dir[i],s,0,0);
		if(i>1){Addedge(clr[i-1],clr[i],INF,0);Addedge(clr[i],clr[i-1],0,0);}
		if(i>tim_f){Addedge(dir[i-tim_f],clr[i],INF,cost_f);Addedge(clr[i],dir[i-tim_f],0,-cost_f);}
		if(i>tim_l){Addedge(dir[i-tim_l],clr[i],INF,cost_l);Addedge(clr[i],dir[i-tim_l],0,-cost_l);}
	}
	printf("%lld\n",network_flow());//最小费用最大流 
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值