经典网络流建模问题(餐巾)

   餐巾

【问题描述】
  一个餐厅在相继的N天里,第i天需要Ri块餐巾(i=l,2,…,N)。餐厅可以从三种途径获得餐巾。
    (1)购买新的餐巾,每块需p分;
    (2)把用过的餐巾送到快洗部,洗一块需m天,费用需f分(f<p)。如m=l时,第一天送到快洗部的餐巾第二天就可以使用了,送慢洗的情况也如此。
    (3)把餐巾送到慢洗部,洗一块需n天(n>m),费用需s分(s<f)。
    在每天结束时,餐厅必须决定多少块用过的餐巾送到快洗部,多少块送慢洗部。在每天开始时,餐厅必须决定是否购买新餐巾及多少,使洗好的和新购的餐巾之和满足当天的需求量Ri,并使N天总的费用最小。
【输入】
    输入文件共3行,第1行为总天数;第2行为每天所需的餐巾块数;第3行为每块餐巾的新购费用p,快洗所需天数m,快洗所需费用f,慢洗所需天数n,慢洗所需费用s。
【输出】
    输出1行为最小的费用。
【样例】
输入:
3
3 2 4
10 1 6 2 3
输出:
64

  很好的一个题目,让我又加深了对最小费用最大流的问题。一开始自己“意淫”了一个最小费用流的图,结果样例一直算的是180,后来才发现自己还是没有理解好最小费用最大流的含义,所谓的“最小费用最大流”,首要保证的条件是最大流如果最优的情况未必是原图达到“最大流”时的情况的话,那么是不能用“最小费用最大流”算法去解决的。

    那么既然上面yy出了问题,不妨就先着手解决这个问题:要将“最大流”赋予怎样的含义(也就是说确定是什么东西达到“最大”)才能保证最优情况是原图达到“最大流”时的情况呢?一种可行的思路就是确定每天使用的餐巾的数量达到“最大”,不管是用的旧的还是新买的,反正总要用那么多,这样最优情况一定是满足“最大流”条件的。那么我们就要继续考虑这些使用的东西是从哪里来的,一部分是新买的,另一部分是用剩下的洗了之后的,新买的好办,直接建个源点流出来费用为P的流即可,那么洗了之后的那部分是怎么来的?肯定首先要满足是剩下的,那么怎么才会剩下呢?每天用了多少就会剩下多少。想到这,其实大部分问题已经解决了,剩下的就是考虑脏的餐巾存放及清洗的问题了,这部分放到建图里一起说吧。

    接下来我们考虑建图,首先将一个点i拆成两个点i和i',一共要建六类边:

    ① (S,i,n,P) :S到i的容量为n(n表示当天需要的餐巾的数量)费用为P的边,表示今天最多买n条新的餐巾,当然把容量搞成INF也无所谓。

    ② (i,T,n,0) :i到T的容量为n费用为0的边,表示今天最多用n条餐巾,通过这样的边保证了每天的最大流都是所需餐巾的数量。

    ③ (S,i',n,0) :S到i'的容量为n费用为0的边,表示今天会剩下n条餐巾,之所以费用为0是因为这些是剩下的,之前肯定已经买过了,所以费用是0。这里的容量就不能搞成INF了,一个原因是因为最后一定至多只会生剩下n条,另一个原因就是这里如果搞成INF会造成错误,因为会凭空多了好多用过的餐巾,而这些餐巾是没有被买过的。

    ④ (i',i+xn,INF,xw) :i'到i+xn(xn表示快洗部洗一条餐巾所需的天数)的容量为INF费用为xw(xw表示快洗部洗一条餐巾所需的费用)的边,表示累计到今天的剩下的部分或者全部的餐巾可以放到快洗部去洗,并由第i+xn天使用。

    ⑤ (i',i+yn,INF,yw) :和上面那类边基本一样,只不过现在是慢洗部了。

    ⑥ (i',(i+1)',INF,0) : i'到(i+1)'的容量为INF费用为0的边,表示第i天用脏了的餐巾可以积攒到第i+1天再做打算。

    建完上面的边之后做最小费用最大流就可以了。

#include<cstdio>
#include<cstring>
#include<deque>
#define N ((2500<<1)+10)*2
#define M 100000+10
#define inf 1e9
using namespace std;
namespace MincostMaxflow{
    int head[N],arnum=1;
    struct Arc{int next,to,cap,cost;}arc[M];
    void add(int from,int to,int cap,int cost){
        arc[++arnum].next=head[from];
        head[from]=arnum;
        arc[arnum].to=to;
        arc[arnum].cap=cap;
        arc[arnum].cost=cost;
    }
    void insert(int from,int to,int cap,int cost){add(from,to,cap,cost);add(to,from,0,-cost);}
    int dis[N],pre[N],way[N],book[N];
    int st,en;
    deque<int>Q;
    int Mincost,Maxflow;
    bool SPFA()
    {
        for(int i=0;i<=N-1;i++)dis[i]=inf;
        memset(pre,0,sizeof(pre));
        memset(way,0,sizeof(way));
        memset(book,0,sizeof(book));
        Q.clear();
        book[st]=1;dis[st]=0;
        Q.push_back(st);
        while(!Q.empty())
        {
            int u=Q.front();book[u]=0;
            Q.pop_front();
            for(int i=head[u];i;i=arc[i].next)
            {
                int v=arc[i].to;
                int cap=arc[i].cap;
                if(cap>0&&dis[v]>dis[u]+arc[i].cost)
                {
                    dis[v]=dis[u]+arc[i].cost;
                    way[v]=u;
                    pre[v]=i;
                    if(!book[v])
                    {
                        book[v]=1;
                        if(Q.empty()||dis[v]>dis[Q.front()])Q.push_back(v);
                        else Q.push_front(v);
                        
                    }
                }
            }
        }
        if(dis[en]==inf)return false;
        int minn=inf;
        for(int i=en;i!=st;i=way[i])
            minn=min(minn,arc[pre[i]].cap);
        Maxflow+=minn;
        Mincost+=minn*dis[en];
        for(int i=en;i!=st;i=way[i])
        {
            arc[pre[i]].cap-=minn;
            arc[pre[i]^1].cap+=minn;
        }
        return true;
    }
}
using namespace MincostMaxflow;
int main()
{
    int D;
    scanf("%d",&D); 
    st=0,en=N-1;
    int day[N];
    for(int i=1;i<=D;i++)scanf("%d",&day[i]);
    int p,m,f,n,s;scanf("%d%d%d%d%d",&p,&m,&f,&n,&s);
    for(int i=1;i<=D;i++){
        insert(st,i,day[i],p);
        insert(i,en,day[i],0);
        insert(st,i+N/2,day[i],0);
        insert(i+N/2,i+m,inf,f);
        insert(i+N/2,i+n,inf,s);
        insert(i+N/2,i+N/2+1,inf,0);
    }
    while(SPFA());
    printf("%d\n",Mincost);
    return 0;
}

 

转载于:https://www.cnblogs.com/star-eternal/p/7635081.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值