费用流+构图——Luogu1251 [网络流24题]餐巾计划问题([HNOI2001]软件开发)

餐巾计划题面:Luogu1251
经典题啊。然后被HN省选拿去当原题考了???
HNOI传送门:BZOJ1221 Luogu2223
除了输入略有出入以外其他包括题面和思路算法都是一样的,所以放在一起写了= =
双倍经验啊韩寒会画画后悔画韩红
首先可以发现这是一个最小费用最大流问题
建图比较麻烦。。。我们把图看成一个餐巾的清洗关系循环图
首先要保证每一天有一定数量的餐巾能够使用,我们设源点s,汇点t

首先是餐巾的购买问题
然后把每一天i拆成没洗过的点和洗过的点,首先从s向每天的洗过的点连边(流INF费p(买一块的钱))

重点来啦!关于餐巾使用后的循环问题

对于每天需要的餐巾,我们从每天的洗过的点向t连边(流a[i](每天需要的块数,下同)费0),然后再从s向每天的没洗过的点连边(流a[i]费0)
这样就形成了餐巾的重复利用循环。。。(可以这么解释吧……也就是说餐巾被用过之后再重新流通到清洗关系循环中)
或者更直观的解释是:餐巾从某一天的洗过的点流到汇点t被使用者使用后这些餐巾被又扔到源点s,然后流通到了这一天的没洗过的点
应该都明白了吧 = =

然后最后就是关于清洗的事情了
a和b都一样的,我们直接从第i天的没洗过的点向第i+a(b)天的洗过的点连边(流INF费fa(fb))
还有一种情况,(没)洗干净的餐巾可以留到下一天使用(清洗),所以再从第i天的(没)洗过的点向第i+1天的(没)洗过的点连边(流INF费0)

为什么这样是对的呢?
首先因为对于所有的没洗过的点连向洗过的点,只有清洗这种途径(购买是从源点s),所以不存在同一天从没洗过的点向洗过的点连边这种事情,这样可以保证循环过程中连边不出现差错。。。
第二,这张图的最大流是已知的了,即所有n天的需要块数之和,所以在这个基础上跑出来的最小费用就是答案了,而这张图设计之巧妙刚好可以满足这个条件

最后就是跑一遍mcmf的事情了
听说EK的被卡了???zkw大法好!
附餐巾计划问题的代码

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <ctime>
#include <map>
#include <queue>
#include <cstdlib>
#include <string>
#include <climits>
#include <set>
#include <vector>
using namespace std;
long long ans=0;bool vis[100001];
int n,a,b,f,fa,fb,s,t,dist[100001];
int nedge=-1,p[200001],c[200001],cc[200001],nex[200001],head[200001];
inline void addedge(int x,int y,int z,int zz){
    p[++nedge]=y;c[nedge]=z;cc[nedge]=zz;nex[nedge]=head[x];head[x]=nedge;
}
inline bool spfa(int s,int t){
    memset(vis,0,sizeof vis);vis[t]=1;
    for(int i=s;i<=t;i++)dist[i]=1e9;dist[t]=0;
    deque<int>q;q.push_back(t);
    while(!q.empty()){
        int now=q.front();q.pop_front();
        for(int k=head[now];k>-1;k=nex[k])if(c[k^1]&&dist[p[k]]>dist[now]-cc[k]){
            dist[p[k]]=dist[now]-cc[k];
            if(!vis[p[k]]){
                vis[p[k]]=1;
                if(!q.empty()&&dist[p[k]]<dist[q.front()])q.push_front(p[k]);
                else q.push_back(p[k]);
            }
        }vis[now]=0;
    }
    return dist[s]<1e9;
}
inline int dfs(int x,int low){
    vis[x]=1;if(x==t)return low;
    int a,used=0;
    for(int k=head[x];k>-1;k=nex[k])if(!vis[p[k]]&&c[k]&&dist[p[k]]==dist[x]-cc[k]){
        a=dfs(p[k],min(c[k],low-used));
        if(a)c[k]-=a,c[k^1]+=a,used+=a,ans+=(long long)a*cc[k];
        if(used==low)break;
    }
    return used;
}
inline int costflow(){
    int flow=0;
    while(spfa(s,t)){
        vis[t]=1;
        while(vis[t]){memset(vis,0,sizeof vis);flow+=dfs(s,1e9);}
    }
    return flow;
}
int main()
{
    memset(nex,-1,sizeof nex);memset(head,-1,sizeof head);
    scanf("%d",&n);s=0;t=2*n+1;
    for(int i=1;i<=n;i++){
        int x;scanf("%d",&x);
        addedge(s,i,x,0);addedge(i,s,0,0);
        addedge(i+n,t,x,0);addedge(t,i+n,0,0);
    }
    scanf("%d%d%d%d%d",&f,&a,&fa,&b,&fb);
    for(int i=1;i<=n;i++){
        addedge(s,i+n,1e9,f);addedge(i+n,s,0,-f);
        if(i<n)addedge(i+n,i+n+1,1e9,0),addedge(i+n+1,i+n,0,0);
        if(i+a<=n)addedge(i,i+n+a,1e9,fa),addedge(i+n+a,i,0,-fa);
        if(i+b<=n)addedge(i,i+n+b,1e9,fb),addedge(i+n+b,i,0,-fb);
    }
    costflow();printf("%lld",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值