[网络流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 \rightarrow i \rightarrow i+n \rightarrow 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\n",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\n",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\n",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\n",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\n",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\n",bound_mcmf::solve(s,t));
}

posted @ 2020-05-13 22:30  birchtree  阅读(272)  评论(0编辑  收藏  举报