1. 程式人生 > >[網絡流24題]餐巾計劃問題——費用流建模

[網絡流24題]餐巾計劃問題——費用流建模

好處 情況 提前 main 然而 clu include 最大流 esp

餐巾計劃問題

不錯的建模題。

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

但是怎麽建模呢?

可以想到,因為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 */

總結:

為什麽要拆點?

因為同一個物品在不同時空下的狀態是不同的,之間的決策也相對獨立,甚至可能存在相互轉移的情況。

當一個點表示已經無能為力的時候,可以考慮拆點。

例如修車、美食節

(化點為邊,,,,嚴格意義上不算是,這只是為了方便統計處理,並不是狀態不同)

只要能處理好拆出來的點之間的關系就好。

[網絡流24題]餐巾計劃問題——費用流建模