[网络流24题] 10.餐巾计划问题 解题报告 (最小费用最大流)
题意
一间餐厅需要连续运营 \(N\) 天, 每天都有不同的餐巾需求量 \(r[i]\),
每天可以有 4 种操作,
- 购买新的餐巾, 每条餐巾的费用为 \(p\).
- 将用过的餐巾保存, 留到下一天.
- 将用过的餐巾送到快洗部, \(m\) 天后拿到餐巾, 每条餐巾的清洗费用为 \(f\).
- 将用过的餐巾送到慢洗部, \(n\) 天后拿到餐巾, 每条餐巾的清洗费用为 \(s\).
求满足每天餐巾需求的总费用最小值.
思路
每天都需要有 \(r[i]\) 条餐巾, 那么考虑从节点 \(i\) 向 \(T\) 连一条容量为 \(r[i]\) 的边, 那我们的大致思路就确定为了 最小费用最大流.
再考虑每天需要的餐巾可以从哪里获得,
- 新购买.
- 从 \(i-m\) 花费 $f/ $条 的代价获得.
- 从 \(i-n\) 花费 \(s/\)条 的代价获得.
那我们再考虑把每一天拆成 供给点 和 需求点, 也可以理解为这一天的 结束 和 开始.
第 \(i\) 天的供给点为 \(i\), 需求点为 \(i+\).N
每个供给点 \(i\) 就需要往外连三条边, 分别对应题意中的后三种操作,
- 连向 \(i+1\), 容量为 \(inf\), 边权为 \(0\), 表示把用过的毛巾留到下一天.
- 连向 \(i+m+N\), 容量为 \(inf\), 边权为 \(f\), 表示把用过的毛巾送到快洗部.
- 连向 \(i+n+N\), 容量为 \(inf\), 边权为 \(s\), 表示把用过的毛巾送到慢洗部.
除此之外, 我们还需要新建一个供给点, 向所有需求点连容量为 \(inf\) ,边权为 \(p\) 的边, 表示当天新购买的毛巾.
图建完后, 跑个费用流即可. (费用流 -- OI Wiki)
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=1e5+7;
const int __=1e7+7;
const ll inf=1e17;
int n,p,fd,fc,sd,sc; // fd,fc,sd,sc 分别对应题目中的 m,f,n,s
int S,T,apl,lst[_],nxt[__],to[__],tot=1;
ll dis[_],w[__],c[__],ans;
bool vis[_];
queue<int> q;
void add(int x,int y,ll cap,ll wgt){
nxt[++tot]=lst[x]; to[tot]=y; c[tot]=cap; w[tot]=wgt; lst[x]=tot;
nxt[++tot]=lst[y]; to[tot]=x; c[tot]=0; w[tot]=-wgt; lst[y]=tot;
}
void init(){
cin>>n; int nd;
S=2*n+1,T=2*n+2,apl=2*n+3;
for(int i=1;i<=n;i++){
scanf("%d",&nd);
add(S,i,(ll)nd,0);
add(i+n,T,(ll)nd,0);
add(apl,i+n,inf,0);
}
scanf("%d%d%d%d%d",&p,&fd,&fc,&sd,&sc);
add(S,apl,inf,p);
for(int i=1;i<=n;i++){
if(i<n) add(i,i+1,inf,0);
if(i+fd<=n) add(i,i+fd+n,inf,fc);
if(i+sd<=n) add(i,i+sd+n,inf,sc);
}
}
bool bfs(){
for(int i=1;i<=apl;i++) dis[i]=inf;
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop();
dis[S]=0; q.push(S);
while(!q.empty()){
int u=q.front(); q.pop();
vis[u]=0;
for(int i=lst[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]<=dis[u]+w[i]||!c[i]) continue;
dis[v]=dis[u]+w[i];
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
return dis[T]!=inf;
}
ll dfs(int u,ll flow){
if(u==T) return flow;
ll rest=flow; vis[u]=1;
for(int i=lst[u];i;i=nxt[i]){
int v=to[i];
if(vis[v]||dis[v]!=dis[u]+w[i]||!c[i]) continue;
ll cst=dfs(v,min(rest,c[i]));
if(cst==0) dis[v]=-inf;
else{
c[i]-=cst; c[i^1]+=cst;
rest-=cst; ans+=cst*w[i];
}
}
vis[u]=0;
return flow-rest;
}
void Dinic(){
ll flow;
while(bfs()){
do{
flow=dfs(S,inf);
}while(flow);
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("x.in","r",stdin);
#endif
init();
Dinic();
printf("%lld\n",ans);
return 0;
}