[网络流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;
}

转载于:https://www.cnblogs.com/Chen-Jr/p/11007131.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值