• [网络流24题]餐巾计划问题(费用流/有上下界的费用流)


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

    题面

    一个餐厅在相继的 N天里,每天需用的餐巾数不尽相同。假设第 (i)天需要(r_i)块餐巾( i=1,2,...,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 p 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 n 天(n>m),其费用为 s 分(s<f)。
    每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。
    试设计一个算法为餐厅合理地安排好 N 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。

    分析

    这应该是网络流24题里思维难度最大的一道题。(不可做的机器人路径规划除外)

    首先考虑本题的约束:每天的餐巾够用,还可以延期送洗。也就是说每天结束时的脏毛巾数(geq)每天使用的干净毛巾数,因为可能有来自前一天的脏毛巾。一般网络流由于流量上界的存在,擅长处理小于等于的问题,而这里是大于等于的问题,因此要么变换约束条件,要么用有上下界的网络流求解。

    一般费用流的建图:

    这里需要用到一个贪心结论:洗了的毛巾都被用了。这是因为如果使用的毛巾有一部分是洗的,那么等到之后再洗好也不迟。如果是买的,之后可以再买。

    这样就可以拆点,(x)表示第(x)天产生的脏餐巾,(x+N)表示第(x)天获得的干净餐巾。又因为网络流的流量守恒有着两重意义:一是除源汇外点的流量守恒,二是源点流出总流量等于汇点汇入总流量,那么我们可以用源汇点(s,t)来分别表示产生脏餐巾和获得的干净餐巾,以保证洗了的毛巾都被用了。接下来就可以写出建图方式

    1. 连边((s,i,r_i,0)),表示产生(r_i)条脏毛巾
    2. 连边((i+N,t,r_i,0))表示获得(r_i)条干净毛巾
    3. 连边((i,i+1,+infin,0))表示延期送洗
    4. 连边((i,i+m+N,+infin,f))表示快洗,(i+m)天会获得干净毛巾
    5. 连边((i,i+n+N,+infin,s))表示慢洗,(i+n)天会获得干净毛巾
    6. 连边((s,i+N,infin,p))表示第(i)天直接购买新毛巾。

    注意我们虽然拆了点,但不能直接将两个拆点相连,用两个拆点相连的边来表示限制。这是因为一般的最小费用流是建立在流量最大的基础上的,由于(s)连到(i),(i+N)连到(t),那么为了最大流,流会直接从(s ightarrow i ightarrow i+n ightarrow t),导致我们用链表示的意义(如本题中的延期送洗)失效。"[SNOI2019]通信"一题也利用了这个思想。


    从有上下界的费用流建图:

    前面提到的一般费用流建图总是有点反直觉(甚至感觉我解释的不太对)。而带上下界的费用流则很好理解。

    同样拆点:第(x)天拆成(x)(x+N).(x)表示每天开始,(x+N)表示每天结束。

    1. 连边((s,i,0,+infin,p))表示买新毛巾
    2. 连边((i,i+n,r_i,+infin,0)).流过这条边表示毛巾在这一天被操作(干净毛巾被使用或脏毛巾被送洗).([r_i ,+infin))表示每天结束时的脏毛巾数(geq)每天使用的干净毛巾数,因为可能有来自前一天延期送洗的脏毛巾。
    3. 连边((i,i+1,0,+infin,0))表示这些衣服在这一天不进行任何操作.即留到下一天再洗或使用。
    4. 连边((i+N,i+m,0,+infin,f))表示这一天结束时送洗毛巾,使用快洗,在第(i+m)天再决定是否被操作。
    5. 连边((i+N,i+n,0,+infin,s))表示这一天结束时送洗毛巾,使用慢洗。
    6. 连边((i+N,t,0,+infin,0))表示兀余的毛巾。

    最小费用可行流即为答案。

    这样看来有上下界的费用流建图很直接。流的意义从头至尾都是相同的,表示毛巾的流动,无论是脏的还是干净的。我们也不需要关心兀余是否有必要,直接建到图里让算法自己判断走不走即可。这样可以大大减少思维量,虽然图中的有些边可能永远不会被流过,且常数较大。

    代码

    费用流

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define maxn 10005
    #define maxm 500005
    #define INF 0x3f3f3f3f 
    using namespace std; 
    int n; 
    struct edge{
        int from;
        int to;
        int next;
        int flow;
        int cost;
    }E[maxm<<1];
    int head[maxn];
    int sz=1;
    void add_edge(int u,int v,int w,int c){
    //	printf("%d->%d vol=%d cost=%d
    ",u,v,w,c);
        sz++;
        E[sz].from=u;
        E[sz].to=v;
        E[sz].next=head[u];
        E[sz].flow=w;
        E[sz].cost=c; 
        head[u]=sz;
        
        sz++;
        E[sz].from=v;
        E[sz].to=u;
        E[sz].next=head[v];
        E[sz].flow=0;
        E[sz].cost=-c; 
        head[v]=sz;
    } 
    
    int dist[maxn];
    int minf[maxn];
    int pre[maxn];
    int inq[maxn]; 
    bool spfa(int s,int t){
        memset(dist,0x3f,sizeof(dist));
        memset(inq,0,sizeof(inq));
        queue<int>q;
        q.push(s);
        inq[s]=1;
        dist[s]=0;
        while(!q.empty()){
            int x=q.front();
            q.pop();
            inq[x]=0; 
            for(int i=head[x];i;i=E[i].next){
                int y=E[i].to;
                if(E[i].flow){
                    if(dist[y]>dist[x]+E[i].cost){
                        dist[y]=dist[x]+E[i].cost;
                        minf[y]=min(minf[x],E[i].flow);
                        pre[y]=i;
                        if(!inq[y]){
                            inq[y]=1;
                            q.push(y);
                        } 
                    }
                }
            }
        } 
        if(dist[t]==INF) return 0;
        else return 1;
    }
    
    void update(int s,int t){
        int x=t;
        while(x!=s){
            int i=pre[x];
            E[i].flow-=minf[t];
            E[i^1].flow+=minf[t];
            x=E[i^1].to;
        }
    }
    
    int mcmf(int s,int t){
        memset(minf,0x3f,sizeof(minf));
        int mincost=0,maxflow=0;
        while(spfa(s,t)){
            update(s,t);
            mincost+=dist[t]*minf[t];
            maxflow+=minf[t];
        }
        return mincost;
    }
    int p,fcost,fday,scost,sday;
    int r[maxn];
    int main(){
    	scanf("%d",&n);
    	scanf("%d %d %d %d %d",&p,&fday,&fcost,&sday,&scost);
    	for(int i=1;i<=n;i++) scanf("%d",&r[i]);
    	int s=0,t=n*2+1;
    	for(int i=1;i<=n;i++){
    		add_edge(s,i,r[i],0);
    		add_edge(i+n,t,r[i],0);
    	}
    	for(int i=1;i<n;i++){
    		add_edge(i,i+1,INF,0);
    	}
    	for(int i=1;i+fday<=n;i++){
    		add_edge(i,i+fday+n,INF,fcost);
    	}
    	for(int i=1;i+sday<=n;i++){
    		add_edge(i,i+sday+n,INF,scost);
    	}
    	for(int i=1;i<=n;i++){
    		add_edge(s,i+n,INF,p);
    	}
    	printf("%d
    ",mcmf(s,t));
    }
    

    有上下界的费用流:

    //https://www.cnblogs.com/five20/p/8417493.html
    //此题会爆int
    //洛谷输入格式和loj不一样 
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define maxn 10005
    #define maxm 500005
    #define INF 0x3f3f3f3f3f3f3f3f
    using namespace std;
    int n;
    typedef long long ll;
    namespace EK {
    	struct edge {
    		int from;
    		int to;
    		int next;
    		ll flow;
    		ll cost;
    	} E[maxm<<1];
    	int head[maxn];
    	int sz=1;
    	void add_edge(int u,int v,ll w,int c) {
    //	printf("%d->%d vol=%d cost=%d
    ",u,v,w,c);
    		sz++;
    		E[sz].from=u;
    		E[sz].to=v;
    		E[sz].next=head[u];
    		E[sz].flow=w;
    		E[sz].cost=c;
    		head[u]=sz;
    		sz++;
    		E[sz].from=v;
    		E[sz].to=u;
    		E[sz].next=head[v];
    		E[sz].flow=0;
    		E[sz].cost=-c;
    		head[v]=sz;
    	}
    	ll dist[maxn];
    	ll minf[maxn];
    	int pre[maxn];
    	int inq[maxn];
    	bool spfa(int s,int t) {
    		memset(dist,0x3f,sizeof(dist));
    		memset(inq,0,sizeof(inq));
    		queue<int>q;
    		q.push(s);
    		inq[s]=1;
    		dist[s]=0;
    		while(!q.empty()) {
    			int x=q.front();
    			q.pop();
    			inq[x]=0;
    			for(int i=head[x]; i; i=E[i].next) {
    				int y=E[i].to;
    				if(E[i].flow) {
    					if(dist[y]>dist[x]+E[i].cost) {
    						dist[y]=dist[x]+E[i].cost;
    						minf[y]=min(minf[x],E[i].flow);
    						pre[y]=i;
    						if(!inq[y]) {
    							inq[y]=1;
    							q.push(y);
    						}
    					}
    				}
    			}
    		}
    		if(dist[t]==INF) return 0;
    		else return 1;
    	}
    	void update(int s,int t) {
    		int x=t;
    		while(x!=s) {
    			int i=pre[x];
    			E[i].flow-=minf[t];
    			E[i^1].flow+=minf[t];
    			x=E[i^1].to;
    		}
    	}
    	pair<ll,ll> mcmf(int s,int t) {
    		memset(minf,0x3f,sizeof(minf));
    		ll mincost=0,maxflow=0;
    		while(spfa(s,t)) {
    			update(s,t);
    			mincost+=dist[t]*minf[t];
    			maxflow+=minf[t];
    		}
    		return make_pair(mincost,maxflow);
    	}
    }
    
    namespace bound_mcmf {
    	using namespace EK;
    	int cntv,cnte;
    	int from[maxm],to[maxm];
    	ll lower[maxm],upper[maxm];
    	ll cost[maxm];
    	ll dflow[maxn];//入流-出流
    	int hash_id[maxm];
    	void adde(int u,int v,ll l,ll r,ll c) {
    //		printf("%d->%d [%d,%d] cost=%d
    ",u,v,l,r,c);
    		cnte++;
    		from[cnte]=u;
    		to[cnte]=v;
    		lower[cnte]=l;
    		upper[cnte]=r;
    		cost[cnte]=c;
    	}
    	ll solve(int s,int t) {
    		ll ans=0;
    		int ss=cntv+1,tt=cntv+2;
    		adde(t,s,0,INF,0);
    		for(int i=1; i<=cnte; i++) {
    			add_edge(from[i],to[i],upper[i]-lower[i],cost[i]);
    			hash_id[i]=sz;
    			dflow[from[i]]-=lower[i];
    			dflow[to[i]]+=lower[i];
    			ans+=cost[i]*lower[i];
    		}
    		ll sum=0;
    		for(int i=1; i<=cntv; i++) {
    			if(dflow[i]<0) {
    //				if(i==1) printf("") 
    				add_edge(i,tt,-dflow[i],0);
    			} else if(dflow[i]>0) {
    				add_edge(ss,i,dflow[i],0);
    				sum+=dflow[i];
    			}
    		}
    		pair<ll,ll> p=mcmf(ss,tt);
    		if(p.second==sum) { 
    //			printf("flow1=%d
    ",p.first);
    			ans+=p.first;
    			E[hash_id[cnte]].flow=E[hash_id[cnte]^1].flow=0;
    			return ans;
    		} else return 0;
    	}
    }
    
    int p,fcost,fday,scost,sday;
    int r[maxn];
    
    int main() {
    	scanf("%d",&n);
    	for(int i=1; i<=n; i++) scanf("%d",&r[i]);
    	scanf("%d %d %d %d %d",&p,&fday,&fcost,&sday,&scost);
    	int s=0,t=n*2+1;
    	bound_mcmf::cntv=n*2+1;
    	//i今天清洗的,i+n今天使用的 
    	for(int i=1; i<=n; i++) {
    		bound_mcmf::adde(s,i,0,INF,p);//买新毛巾 
    	}
    	for(int i=1; i<n; i++) {
    		bound_mcmf::adde(i,i+1,0,INF,0);//脏毛巾可以留到下一天再洗 
    	}
    	for(int i=1; i<=n; i++) {
    		bound_mcmf::adde(i,i+n,r[i],INF,0);
    		//当天洗的>=用的 
    	} 
    	for(int i=1;i<=n;i++){
    		bound_mcmf::adde(i+n,t,0,r[i],0);
    	}
    	for(int i=1; i+fday<=n; i++) {
    		bound_mcmf::adde(i+n,i+fday,0,INF,fcost);
    	}
    	for(int i=1; i+sday<=n; i++) {
    		bound_mcmf::adde(i+n,i+sday,0,INF,scost);
    	}
    	printf("%lld
    ",bound_mcmf::solve(s,t));
    }
    
    
  • 相关阅读:
    测试用例的优先级的概念
    Day02.测试用例和测试方法
    day01.测试理论
    开发python 面试题
    4.路径页面接口开发
    ps命令没有显示路径找到命令真实路径
    Linux软链接和硬链接
    Linux文件元数据和节点表结构
    jinjia2语言
    Ansible之YAML语言
  • 原文地址:https://www.cnblogs.com/birchtree/p/12885360.html
Copyright © 2020-2023  润新知