订货[题解]
订货
题目描述
有一个公司,要做 \(n\) 个月的订购计划,具体如下
-
第 \(i\) 个月对某个产品的需求量是 \(U_i\) ,且我们已知第 \(i\) 个月该产品的订货单价为 \(d_i\) 。
-
上一个月没有销售完的产品可以被贮藏起来,但是需要支付 \(m\) 的贮藏费用
-
第一个月月初的库存为零,且第 \(n\) 个月的库存量也为零
问如何安排订购计划,才能使订购成本最低。
题目分析
这道题作为一道费用流题应该还是挺明显的,而费用流模板想必就不用加以赘述,毕竟对于网络流,建图才是精髓。
显然本题只有 \(n\) 个实点,对应 \(n\) 个月,然后我们能够发现,如果通过建立虚点我们剩余的工作将完全无法展开。
考虑一个常规想法,建立一个超级源点,一个超级汇点,则建图方案具体如下:
-
可以将超级源点向代表月份点 \(i\) 连接一条流量是正无穷,费用是 \(d_i\)
-
贮存可以具体化为月份\(i\)向月份 \(i+1\) 连接一条流量是 \(s\) ,费用是 \(m\) 的边
-
最后,再将月份 \(i\) 向超级汇点连接一条流量是 \(U_i\) 费用是 \(0\) 的边
样例建图如下:
CODE
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int N=5e3+10,EDGE=1e5+10;
int n,m,s,t;
ll maxf,minc;
inline ll read()
{
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
ll w[EDGE],p[EDGE];
int tot=1,nex[EDGE],first[EDGE],v[EDGE];
inline void Add(int x,int y,ll z,ll c)
{
nex[++tot]=first[x];
first[x]=tot;
v[tot]=y,w[tot]=z,p[tot]=c;
}
int pre[N];
ll dis[N],Min[N]; bool vis[N];
inline bool SPFA(int S,int T)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
queue<int> q;
q.push(S);
vis[S]=1,dis[S]=0,Min[S]=INF;
while(!q.empty()){
int now=q.front(); q.pop();
vis[now]=false;
for(register int i=first[now];i;i=nex[i]){
int to=v[i];
if(!w[i]) continue;
if(dis[to]>dis[now]+p[i]){
dis[to]=dis[now]+p[i];
Min[to]=min(Min[now],w[i]);
pre[to]=i;
if(!vis[to]) q.push(to),vis[to]=true;
}
}
}
return dis[T]!=INF;
}
inline void dinic()
{
while(SPFA(0,n+1)){
minc+=dis[n+1]*Min[n+1];
int temp=n+1,i;
while(temp!=0){
i=pre[temp];
w[i]-=Min[n+1];
w[i^1]+=Min[n+1];
temp=v[i^1];
}
}
}
int main()
{
n=read(),m=read(),s=read();
for(register int i=1;i<=n;i++){
int need=read();
Add(i,n+1,need,0),Add(n+1,i,0,0); //连接卖出边
}
for(register int i=1;i<=n;i++){
int temp=read();
Add(0,i,INF,temp),Add(i,0,0,-temp); //连接进货边
}
for(register int i=1;i<n;i++) Add(i,i+1,s,m),Add(i+1,i,0,-m); //连接储存边
dinic();
printf("%lld\n",minc);
return 0;
}