zoukankan      html  css  js  c++  java
  • [网络流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
    */

     总结:

    为什么要拆点?

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

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

    例如修车、美食节

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

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

  • 相关阅读:
    洛谷P2504 [HAOI2006]聪明的猴子
    洛谷P1516 青蛙的约会
    洛谷P1991 无线通讯网
    洛谷P1265 公路修建
    可展开的列表组件
    网格视图(GridView)功能和用法
    自动完成文本框的功能和用法
    扩展BaseAdapter实现不存储列表项的ListView
    使用SimpleAdapter创建ListView
    实例:基于ListActivity实现列表
  • 原文地址:https://www.cnblogs.com/Miracevin/p/10030176.html
Copyright © 2011-2022 走看看