题意
给出6个正整数n,p,N,f,M,s和一个长度为n的数组表示,一家餐馆会连续开n天,第i天需要的餐巾数量为a[i],购买一张新的餐巾的费用是p元,但是这样太浪费了。所以说可以考虑把用过的餐巾拿去洗,然后再用。
可以把餐巾放在快洗部,这样在第x天送过去的餐巾会在第x+N天洗完,一个餐巾的花费是f。
也可以把餐巾放在慢洗部,这样在第x天送过去的餐巾会在第x+M天洗完,一个餐巾的花费是s
要求每天餐馆里有的餐巾一定>=a[i],求最小费用。
值得注意的是 不管需求是多少,所有的餐布都会变脏。
分析
最小费用最大流登场——!
首先来介绍这个算法的实现过程吧。
简单来说就是一个SPFA和一个类似于dinic里面的dfs一样的东西(然而zkw费用流大概就是把dfs里面的增广改成多路增广什么的)
总之还是先来写比较简单的做法吧[?]
首先是要在满足最大流的条件下,满足费用最小,那么可以贪心的考虑,在每一次增广的时候都选择费用最小的一条路来增广。那么就可以把bfs改成spfa来跑一次最短路。(为什么不能是dijkstra呢?因为每次加边的时候是正边费用cost,反边费用-cost,这么做的原因就是如果要换回去增广的话,就要把原来的费用减去(大概是这么解释吧))
然后就是dfs进行增广,值得注意的是可以对于每一个点记录它是被哪一个边更新过来的,然后在dfs的时候其实就是近似于一条链的扫过来就好了。
然后这样就是最小费用最大流的模板了。orz
然后来分析这一道题目吧。
要求每一天的餐巾数量一定要到达多少,可以限制为流量一样的东西。
然后用过的餐巾可以洗掉放在更之后使用。
就是要求每一天都满流嘛…?
add(s,i,inf,p); //每一天都可以买无限张新的餐巾
add(s,i+n,a[i],0); //要求每一天至少有a[i]张餐巾
if (i+1<=n)add(i+n,i+1+n,inf,0); //留着第二天洗
if (i+_n<=n)add(i+n,i+_n,inf,_s); //送去慢洗
if (i+_m<=n)add(i+n,i+_m,inf,_f); //送去快洗
add(i,t,a[i],0); //要求每一天至少有a[i]张餐巾
结束。
code
#include<bits/stdc++.h>
#define M 5005
#define inf 1000000000
using namespace std;
void read(int &x){
x=0; char c=getchar();
for (;c<48;c=getchar());
for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
}
struct ed{
int x,cap,cost,nx;
}e[60005];
int nx[M],ecnt;
void add(int x,int y,int cap,int cost){
e[ecnt]=(ed){y,cap,cost,nx[x]};
nx[x]=ecnt++;
e[ecnt]=(ed){x,0,-cost,nx[y]};
nx[y]=ecnt++;
}
struct EK_EK{
int Flow,Cost;
int dis[M],Q[M],nxt[M],s,t,l,r;
bool vis[M];
bool spfa(int x){
memset(dis,63,sizeof(dis));
l=r=0;
dis[Q[r++]=x]=0;
for (;l!=r;){
x=Q[l++]; l%=M;
vis[x]=0;
for (int i=nx[x];~i;i=e[i].nx)if (e[i].cap>0&&dis[x]+e[i].cost<dis[e[i].x]){
dis[e[i].x]=dis[x]+e[i].cost; nxt[e[i].x]=i;
if (!vis[e[i].x]){
vis[e[i].x]=1;
Q[r++]=e[i].x;
r%=M;
}
}
}
return dis[t]<inf;
}
int dfs(int x,int f){
if(x==s){
Cost+=f*dis[t];
return f;
}
int res=dfs(e[nxt[x]^1].x,min(e[nxt[x]].cap,f));
e[nxt[x]].cap-=res; e[nxt[x]^1].cap+=res;
return res;
}
void solve(int ss,int tt){
s=ss; t=tt;
for (;spfa(s);)Flow+=dfs(t,inf);
}
}EK;
int a[M];
int main(){
// freopen("LOJ6008.in","r",stdin);
memset(nx,-1,sizeof(nx));
int n,p,_m,_f,_n,_s;
read(n); read(p); read(_m); read(_f); read(_n); read(_s);
int s=0,t=2*n+1;
for (int i=1;i<=n;i++)read(a[i]);
for (int i=1;i<=n;i++){
add(s,i,inf,p);
add(s,i+n,a[i],0);
if (i+1<=n)add(i+n,i+1+n,inf,0);
if (i+_n<=n)add(i+n,i+_n,inf,_s);
if (i+_m<=n)add(i+n,i+_m,inf,_f);
add(i,t,a[i],0);
}
EK.solve(s,t);
printf("%d\n",EK.Cost);
return 0;
}
总结
WA了两发很难受啊。
说点要注意的好了。
1.建边的时候一定要注意cost一正一负。
2.对于spfa采用循环队列的时候千万不能吧l!=r达成l