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

传送门

题意:

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

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

分析:

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

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

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

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

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

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

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

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

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

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

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

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

代码:

#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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值