[网络流24题][CODEVS1237]餐巾计划问题(费用流)

题目描述

传送门

题解

拆点,把每天的点拆成xi和yi,xi表示每一天的脏毛巾,yi表示每一天的新毛巾。
从超级源向xi连边,容量为ri,费用为0;
从yi向超级汇连边,容量为ri,费用为0;
从超级源向yi连边,容量为INF,费用为p;
从xi分别向y(i+m)连边(i+m<=N),容量为INF,费用为f;
从xi分别向y(i+n)连边(i+n<=N),容量为INF,费用为m;
从xi向x(i+1)连边,容量为INF,费用为0;
以上建图分别表示:每天产生的脏毛巾;每天需要准备的新毛巾;每天新买进的毛巾;快洗毛巾;慢洗毛巾;延期送洗。
用最小费用最大流求解即可。
这道题的处理方法及其巧妙,注重理解。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;

const int max_N=1005;
const int max_Max=max_N*2+2;
const int max_m=max_Max*20;
const int max_e=max_m*2;
const int inf=1e9;

int N,p,m,f,n,s,Max,maxflow,mincost,r[max_N];
int point[max_Max],next[max_e],v[max_e],remain[max_e],c[max_e],tot;
int last[max_Max],dis[max_Max];
bool vis[max_Max];
queue <int> q;

inline void addedge(int x,int y,int cap,int z){
    ++tot; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=cap; c[tot]=z;
    ++tot; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; c[tot]=-z;
}

inline int addflow(int s,int t){
    int ans=inf,now=t;
    while (now!=s){
        ans=min(ans,remain[last[now]]);
        now=v[last[now]^1];
    }

    now=t;
    while (now!=s){
        remain[last[now]]-=ans;
        remain[last[now]^1]+=ans;
        now=v[last[now]^1];
    }

    return ans;
}

inline bool bfs(int s,int t){
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    vis[s]=true;
    while (!q.empty()) q.pop();
    q.push(s);

    while (!q.empty()){
        int now=q.front(); q.pop();
        vis[now]=false;
        for (int i=point[now];i!=-1;i=next[i])
          if (dis[v[i]]>dis[now]+c[i]&&remain[i]){
            dis[v[i]]=dis[now]+c[i];
            last[v[i]]=i;
            if (!vis[v[i]]){
                vis[v[i]]=true;
                q.push(v[i]);
            }
          }
    }

    if (dis[t]>inf) return false;
    int flow=addflow(s,t);
    maxflow+=flow;
    mincost+=flow*dis[t];
    return true;
}

inline void major(int s,int t){
    maxflow=0; mincost=0;
    while (bfs(s,t));
}

int main(){
    tot=-1;
    memset(point,-1,sizeof(point));
    memset(next,-1,sizeof(next));

    scanf("%d%d%d%d%d%d",&N,&p,&m,&f,&n,&s);
    for (int i=1;i<=N;++i)
      scanf("%d",&r[i]);
    Max=N*2+2;

    //从超级源向每天的脏毛巾连边,费用0,容量ri 
    for (int i=1;i<=N;++i)
      addedge(1,1+i,r[i],0);

    //从每天的新毛巾向超级汇连边,费用0,容量ri 
    for (int i=1;i<=N;++i)
      addedge(1+N+i,Max,r[i],0);

    //从超级源向每天的新毛巾连边,费用p,容量INF 
    for (int i=1;i<=N;++i)
      addedge(1,1+N+i,inf,p);

    //从每天的脏毛巾向下一天的脏毛巾连边(延期送洗),费用0,容量INF 
    for (int i=1;i<N;++i)
      addedge(1+i,1+i+1,inf,0);

    //从每天的脏毛巾向快洗或慢洗完成后的一天的新毛巾连边,费用为快洗或慢洗的费用,容量INF 
    for (int i=1;i<=N;++i){
        if (i+m<=N)
          addedge(1+i,1+N+i+m,inf,f);
        if (i+n<=N)
          addedge(1+i,1+N+i+n,inf,s);
    }

    major(1,Max);
    printf("%d\n",mincost);
}

总结

①最小费用最大流是在保证最大流的基础上实现最小费用。那么我们就可以把最大流看成一个控制条件。意思就是满足题意的解必须建立在跑满最大流的基础之上。
②没有严格限制的边容量都可以设为INF,刚开始看起来貌似不科学,实际上有了该控制的条件的控制,这些边都可以看成是一种保证流量的关系,费用的话视情况而定。

你可能感兴趣的:(网络流,codevs)