[网络流24题]餐巾计划问题——费用流建模

餐巾计划问题

 

不错的建模题。

 

满足餐巾需求之下,花费最小。可以想到费用流。

但是怎么建模呢?

可以想到,因为N<=2000,而且一切的工作,洗刷,购买都和天有关系。

所以,肯定要把网络流中的点看做每一天。

 

比较麻烦的是,我们不好处理餐巾的干净和脏的状态,

如果每天只有“一个点”的话,我们也不能处理一个餐巾由干净变成脏的情况。(我们不能区分,一个用完的脏餐巾往下传递,和一个干净餐巾留到明天)

所以,考虑把天拆点。

 

那么,自然而然地,

一天拆成早上和晚上。

晚上会获得早上用完的脏毛巾

早上有的毛巾必须是干净的,才能用。

这样连边:

1.S向每个晚上连流ri费0,表示每天晚上会获得这么多的脏毛巾

2.每个早上向T连流ri费0,表示每天早上会用这么多的干净毛巾,并且可以限制最大流一定是sigma(ri)

3.购买,洗刷就比较自然了。

①S向每个早上,连接流inf费p,意义即买

②晚上的i,向走早上的i+day1连接流inf费cost1 ,意义即快洗部。慢洗部同理

4.还有一个情况是,可能我脏毛巾留着,等到最后要洗的时候,才拿去洗

所以,晚上i向i+1连流inf费0的边。

(当然,你也可以比较勤奋,反正以后要用,今天就先洗了。

早上i向i+1连流inf费0的边也可以。,

意义即,干净的毛巾留下来。

其实就是一个平行四边形。。

黑色是脏毛巾留下来,

红色是干净毛巾留下来。

一个提前洗,一个到时候再洗

 

这样,

我可以实现:干净毛巾转化成脏毛巾,脏毛巾洗成干净毛巾,购买干净毛巾,脏毛巾的积累。

跑费用流即可e

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=4005;
const int M=2000*6+23;
const ll inf=0x3f3f3f3f3f3f3f3fll;
struct node{
    int nxt,to;
    ll w,v;
}e[2*M];
int hd[N],cnt=1;
int n,m,s,t;
ll p,d1,c1,d2,c2;
void add(int x,int y,ll z,ll c){
    e[++cnt].nxt=hd[x];e[cnt].to=y;
    e[cnt].w=z;e[cnt].v=c;
    hd[x]=cnt;
    
    e[++cnt].nxt=hd[y];e[cnt].to=x;
    e[cnt].w=0;e[cnt].v=-c;
    hd[y]=cnt;
}
int pre[N];
ll incf[N];
ll d[N];
queue<int>q;
bool in[N];
bool spfa(){
    while(!q.empty()) q.pop();
    memset(d,0x3f,sizeof d);
    d[s]=0;
    q.push(s);
    pre[s]=0;incf[s]=inf;
    while(!q.empty()){
        int x=q.front();q.pop();
        in[x]=0;
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(e[i].w){
                if(d[y]>d[x]+e[i].v){
                    d[y]=d[x]+e[i].v;
                    pre[y]=i;
                    incf[y]=min(incf[x],e[i].w);
                    if(!in[y]){
                        in[y]=1;
                        q.push(y);
                    }
                }
            }
        }
    }
    if(d[t]==inf) return false;
    return true;
}
ll ans;
void upda(){
    int x=t;
    while(pre[x]){
        e[pre[x]].w-=incf[t];
        e[pre[x]^1].w+=incf[t];
        x=e[pre[x]^1].to;
    }
    ans+=d[t]*incf[t];
}
int main(){
    rd(n);
    s=2*n+1,t=2*n+2;
    ll x;
    for(reg i=1;i<=n;++i){
        scanf("%lld",&x);
        add(s,i,x,0);
        add(i+n,t,x,0);
    }
    scanf("%lld%lld%lld%lld%lld",&p,&d1,&c1,&d2,&c2);
    for(reg i=1;i<=n;++i){
        add(s,i+n,0x3f3f3f3f,p);
        if(i+d1<=n) add(i,i+n+d1,inf,c1);
        if(i+d2<=n) add(i,i+n+d2,inf,c2);
        if(i+1<=n) add(i,i+1,inf,0);
    }
    while(spfa()) upda();
    printf("%lld",ans);
    return 0;
}

}
int main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/11/28 8:21:33
*/

 总结:

为什么要拆点?

因为同一个物品在不同时空下的状态是不同的,之间的决策也相对独立,甚至可能存在相互转移的情况。

当一个点表示已经无能为力的时候,可以考虑拆点。

例如修车、美食节

(化点为边,,,,严格意义上不算是,这只是为了方便统计处理,并不是状态不同)

只要能处理好拆出来的点之间的关系就好。

原文地址:https://www.cnblogs.com/Miracevin/p/10030176.html