「网络流 24 题」[9] 餐巾计划

题意

给出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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值