[网络流24题(2/24)] 餐巾计划问题 (洛谷P1251)最小费用最大流

 

传送门

题意:

现在有$N$天,第$i$天需要使用$a_i$个新的餐巾,新餐巾用完后,可以花费$f$元使得第$i+m$天后可以重新利用;可以花费$s$元使得第$i+n$天后可以使用;也可以把旧餐巾留到下一天去洗。

现在买一个新的餐巾需要$p$元,现在问你在满足所有天的要求的前提下的最小花费。

分析:

一个比较厉害的最小费用最大流的模型。

看到即涉及物品的(物品还能流动的),又涉及最小费用的往费用流上考虑?

首先需要知道,在这$N$天中,餐巾是会不断的移动的,我们试着把餐巾的移动路线分析一下:

我们发现,对于每一天,存在着旧餐巾和新餐巾两种叠加的状态,因此我们考虑把一天拆成两个点(分别为旧餐巾和新餐巾)分别来考虑。

对于第$i$天的旧餐巾:

1. 它能够走向第$i+1$的旧餐巾处,无花费
2. 它能够通过花费$f$元,重新洗涤,到达第$i+m$天的新餐巾处
3. 它能够通过花费$s$元,重新洗涤,到达第$i+n$天的新餐巾处

对于第$i$天的新餐巾:

1. 它能通过花费$p_i$元,从源点购买$a_i$个全新的餐巾。
2. 它能通过$i-m$天的旧餐巾洗涤过来。
3. 它能通过$i-n$天的旧餐巾洗涤过来。

而我们发现,对于旧餐巾,它每天都是会通过某种途径源源不断的获取$a_i$的旧餐巾(因为每天都会有新餐巾用完变成旧餐巾)。而上述并没有流向旧餐巾的状态,因此我们考虑从源点向每一天的旧餐巾的状态连接一条容量为$a_i$,花费为$0$的边。

同时,我们发现,新餐巾是远远不断的送出餐巾,同理,我们考虑从每天的新餐巾向汇点连接一天容量为$a_i$,花费为$0$的边,这条边代表着每天都会使用掉$a_i$个餐巾。

而按照我们上述建出来的图,显然是满足流量平衡的,因此,当我们使这张图获取到最大流时,即可满足每一天的餐巾需求均能够被满足。

之后我们只需要在这个基础上跑一遍最小费用最大流即是答案。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn =5005;
const int maxm=500005;
const ll inf=0x3f3f3f3f3f3f3f3f;
int head[maxn],cnt=0;
int vis[maxn],sp,ep;
ll maxflow,cost,dis[maxn];
struct Node{
    int to,next;
    ll val,cost;
}q[maxm<<1];
void init(){
    memset(head,-1,sizeof(head));
    cnt=2;
    maxflow=cost=0;
}
void addedge(int from,int to,ll val,ll cost){
    q[cnt].to=to;
    q[cnt].next=head[from];
    q[cnt].val=val;
    q[cnt].cost=cost;
    head[from]=cnt++;
}
void add_edge(int from,int to,ll val,ll cost){
    addedge(from,to,val,cost);
    addedge(to,from,0,-cost);
}
bool spfa(){
    memset(vis,0,sizeof(vis));
    memset(dis,inf,sizeof(dis));
    dis[sp]=0;
    vis[sp]=1;
    queue<int>que;
    que.push(sp);
    while(!que.empty()){
        int x=que.front();
        que.pop();
        vis[x]=0;
        for(int i=head[x];i!=-1;i=q[i].next){
            int to=q[i].to;
            if(dis[to]>dis[x]+q[i].cost&&q[i].val){
                dis[to]=dis[x]+q[i].cost;
                if(!vis[to]){
                    que.push(to);
                    vis[to]=1;
                }
            }
        }
    }
    return dis[ep]!=inf;
}
ll dfs(int x,ll flow){
    if(x==ep){
        vis[ep]=1;
        maxflow+=flow;
        return flow;
    }//可以到达t,加流
    ll used=0;//该条路径可用流量
    vis[x]=1;
    for(int i=head[x];i!=-1;i=q[i].next){
        int to=q[i].to;
        if((vis[to]==0||to==ep)&&q[i].val!=0&&dis[to]==dis[x]+q[i].cost){
            ll minflow=dfs(to,min(flow-used,q[i].val));
            if(minflow!=0){
                cost+=q[i].cost*minflow;
                q[i].val-=minflow;
                q[i^1].val+=minflow;
                used+=minflow;
            }
            //可以到达t,加费用,扣流量
            if(used==flow)break;
        }
    }
    return used;
}
ll mincostmaxflow(){
    while(spfa()){
        vis[ep]=1;
        while(vis[ep]){
            memset(vis,0,sizeof(vis));
            dfs(sp,inf);
        }
    }
    return maxflow;
}
int main()
{
    int n,p,m,f,N,s;
    scanf("%d",&n);
    sp=0,ep=2*n+1;
    init();
    for(int i=1;i<=n;i++){
        int num;
        scanf("%d",&num);
        add_edge(sp,i+n,num,0);
        add_edge(i,ep,num,0);
    }
    scanf("%d%d%d%d%d",&p,&m,&f,&N,&s);
    for(int i=1;i<=n;i++){
        if(i+1<=n) add_edge(i+n,i+n+1,inf,0);
        if(i+m<=n) add_edge(i+n,i+m,inf,f);
        if(i+N<=n) add_edge(i+n,i+N,inf,s);
        add_edge(sp,i,inf,p);
    }
    mincostmaxflow();
    printf("%lld\n",cost);
    return 0;
}
posted @ 2019-05-28 22:36  ChenJr  阅读(119)  评论(0编辑  收藏  举报