洛谷P1251 餐巾计划问题——费用流

    技术2022-07-11  86

    洛谷P1251 餐巾计划问题——费用流

    题目介绍题目描述输入格式输出格式输入输出样例 题解代码

    题目介绍

    链接: 传送门.

    题目描述

    一个餐厅在相继的 N 天里,每天需用的餐巾数不尽相同。假设第 i天需要 ri​块餐巾( i=1,2,…,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 p 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 n天(n>m),其费用为 s 分(s<f)。 每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。 试设计一个算法为餐厅合理地安排好 N天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。

    输入格式

    由标准输入提供输入数据。文件第 1 行有 1 个正整数 N,代表要安排餐巾使用计划的天数。 接下来的一行是餐厅在相继的 N天里,每天需用的餐巾数。 最后一行包含5个正整数p,m,f,n,s。p 是每块新餐巾的费用; m 是快洗部洗一块餐巾需用天数; f是快洗部洗一块餐巾需要的费用; n是慢洗部洗一块餐巾需用天数; s 是慢洗部洗一块餐巾需要的费用。

    输出格式

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

    输入输出样例

    输入 #1 3 1 7 5 11 2 2 3 1

    输出 #1 134

    说明/提示 N<=2000 ri<=10000000 p,f,s<=10000 时限4s

    题解

    可以看出是费用流的题目。关键在于建网络 首先关注网络的性质: (1)限制每一天使用餐巾数量:需要从源点连边,或者向汇点连容量为限制数量的边。 (2)每一天开始使用干净的毛巾,可以多种来源(来源数量不易约束),故以出边限制每日使用数量;结束时需处理脏毛巾,出边种类多,以入边容量限制。 (3)脏毛巾可以慢洗后+m天使用,或快洗+n天后使用,或留到下一天仍为脏毛巾 (4)干净毛巾除了(3)洗获得,也可以直接购买

    建立网络如下: (1)每一天结点分为开始和结束结点;开始结点向超级汇点T连边,容量为r[i],cost=0;超级源点向结束结点连边,容量r[i],cost=0 (2)对每一天结束结点 a.慢洗,向+m天后开始结点连边,容量INF,cost=f; b.快洗,向+n天后开始结点连边,容量INF,cost=s c.延迟洗,向+1天后的结束结点连边,容量为INF,cost=0 (3)对每一开始结点,还可以直接购买,由源点S向其连边,容量INF,cost=p;

    建立网络后最小费用最大流即可。

    代码

    #include<cstdio> #include<queue> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int maxn=2005; const ll INF=0x3f3f3f3f3f3f3f3f; const int inf=0x3f; class Edge{ public: ll to,cap,cost,nxt,rev; }; int head[maxn<<1]; Edge edge[maxn<<5]; int cnt=1; int N,r,S,T; void add(ll u,ll v, ll cap, ll cost){ edge[cnt].to=v;edge[cnt].cap=cap;edge[cnt].cost=cost;edge[cnt].nxt=head[u];edge[cnt].rev=cnt+1; head[u]=cnt++; edge[cnt].to=u;edge[cnt].cap=0; edge[cnt].cost=-cost;edge[cnt].nxt=head[v];edge[cnt].rev=cnt-1; head[v]=cnt++; } bool vis[maxn<<1]; int pre[maxn<<1],preEdge[maxn<<1]; ll dis[maxn<<1]; ll flow[maxn<<1]; ll h[maxn<<1]; bool Dkjistra(int S, int T){ memset(vis,false,sizeof(vis)); memset(dis, inf, sizeof(dis)); //dis为引入势之后的最短距离,h为真实的距离 memset(flow, inf, sizeof(flow)); priority_queue<pair<int, int> > que; dis[S] = 0; que.push(make_pair(-dis[S], S) ); //优先队列,注意取负值 while(!que.empty() ){ int u = que.top().second; que.pop(); if(vis[u]) continue; //因每次更新都需要入队,需判断是否访问过 vis[u] = true; for(int i=head[u]; i; i=edge[i].nxt){ Edge &e = edge[i]; if(vis[e.to]) continue; if(e.cap>0 && dis[e.to] > dis[u] + e.cost + h[u] - h[e.to]){ dis[e.to] = dis[u] + e.cost + h[u] - h[e.to]; pre[e.to] = u; //记录前驱结点 preEdge[e.to] = i; //记录前驱边 flow[e.to] = min(flow[u], e.cap); que.push(make_pair(-dis[e.to], e.to)); //入队 } } } // h[i]为真实最短路权值 for(int i=1;i<=T;i++) h[i] += dis[i]; return dis[T] != INF; } void MCMF(){ ll maxflow = 0; ll mincost = 0; while(Dkjistra(S, T)){ //将Dinic算法的分层找最短增广路,改为Dijkstra找费用最低路径 maxflow += flow[T]; mincost += flow[T] * h[T]; //h[T]为从S->T路径上单位流量总的cost int now = T; while(now!=S){ //从汇点回溯到源点 Edge &e = edge[preEdge[now]]; e.cap -= flow[T]; //更新残余网络 edge[e.rev].cap += flow[T]; now = pre[now]; } } printf("%lld\n", mincost); } int main(){ scanf("%d",&N); S=2*N+1,T=2*N+2; for(int i=1;i<=N;i++){ scanf("%d",&r); // 超级源点向结束结点连边,容量r费用0 add(S,i+N,r,0); // 起始点向T连边,容量r费用0 add(i,T,r,0); } int p,m,f,n,s; scanf("%d%d%d%d%d",&p,&m,&f,&n,&s); for(int i=1;i<=N;i++){ // 起始结点向S购买 add(S,i,INF,p); if(i+m<=N) add(i+N,i+m,INF,f); //慢洗 if(i+n<=N) add(i+N,i+n,INF,s); //快洗 if(i+1<=N) add(i+N,i+N+1,INF,0); //延迟;结束结点练到下一天结束结点 } MCMF(); }
    Processed: 0.013, SQL: 9