【图论】【网络流】费用流模型

9 篇文章 0 订阅
4 篇文章 0 订阅

费用流板子

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;

const int N=5010,M=100100;

int n,m,S,T,cnt=1;
int dis[N],vis[N],incf[N],pre[N],head[N];
struct node{
	int to,next,w,c;
}e[M];

int read(){
	int x=0,f=1; char ch=getchar();
	if(ch=='-') f=-1, ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	return x*f;
}

void add(int u,int v,int w,int c){
	e[++cnt].to =v; e[cnt].next =head[u]; e[cnt].w=w; e[cnt].c=c; head[u]=cnt;
	e[++cnt].to =u; e[cnt].next =head[v]; e[cnt].w=0; e[cnt].c=-c; head[v]=cnt;
}

bool spfa(){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(incf,0,sizeof incf);
	q.push(S); dis[S]=0; incf[S]=inf;
	while(!q.empty()){
		int u=q.front(); q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].next ){
			int v=e[i].to ;
			if(e[i].w&&dis[v]>dis[u]+e[i].c){
				dis[v]=dis[u]+e[i].c;
				pre[v]=i;
				incf[v]=min(e[i].w,incf[u]);
				if(!vis[v]){
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	return incf[T]>0;
}

void EK(int& flow, int& cost){
	flow=cost=0;
	while(spfa()){
		int t=incf[T];
		flow+=t; cost+=t*dis[T];
		for(int i=T;i!=S;i=e[pre[i]^1].to){
			e[pre[i]].w-=t;
			e[pre[i]^1].w+=t;
		}
	}
}

int main(){
	n=read(); m=read(); S=read(); T=read();
	for(int i=1;i<=m;i++){
		int u=read(), v=read(), w=read(), c=read();
		add(u,v,w,c);
	}
	int flow,cost; 
	EK(flow,cost);
	printf("%d %d",flow,cost);
	
	return 0;
}

费用流直接应用

运输问题

AcWing 2192. 运输问题

W 公司有 m 个仓库和 n 个零售商店。
第 i 个仓库有 ai 个单位的货物;第 j 个零售商店需要 bj 个单位的货物。
货物供需平衡,即 ∑ i = 1 m a i = ∑ j = 1 n b j ∑_{i=1}^{m}a_i=∑_{j=1}^{n}b_j i=1mai=j=1nbj
从第 i 个仓库运送每单位货物到第 j 个零售商店的费用为 cij。
试设计一个将仓库中所有货物运送到零售商店的运输方案。
对于给定的 m 个仓库和 n 个零售商店间运送货物的费用,计算最优运输方案和最差运输方案。

S连仓库(aij,0)商店连T(bij,0),每个仓库和商店之间连(inf,Cij)
本题建图比较显然

void add(int u,int v,int w,int c){
    e[++cnt].to=v; e[cnt].next=head[u]; e[cnt].w=w; e[cnt].c=c; head[u]=cnt;
    e[++cnt].to=u; e[cnt].next=head[v]; e[cnt].w=0; e[cnt].c=-c; head[v]=cnt;
}

bool spfa(){}
int EK(){}

int main(){
    m=read(); n=read();
    S=N-1, T=N-2;
    for(int i=1;i<=m;i++){
        int a=read();
        add(S,i,a,0);
    }
    for(int i=1;i<=n;i++){
        int a=read();
        add(m+i,T,a,0);
    }

    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            int a=read();
            add(i,m+j,inf,a);
        }
    }
    printf("%d\n",EK());
    for(int i=2;i<=cnt;i+=2){
        e[i].w+=e[i^1].w;
        e[i^1].w=0;
        e[i].c=-e[i].c; 
        e[i^1].c=-e[i^1].c;
    }
    printf("%d\n",-EK());
}
负载平衡问题

AcWing 2194. 负载平衡问题

G 公司有 n 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。
如何用最少搬运量可以使 n 个仓库的库存数量相同
搬运货物时,只能在相邻的仓库之间搬运。

要使所有仓库最终的库存数量相同,为x=tot/n,则根据贪心的思想,只需从货物多的仓库像货物少的仓库搬运。
对于所有库存数量低于最终平均数量x的仓库,从源点S向其连边(a[i]-x,0)
对于所有库存数量高于最终平均数量x的仓库,从其向汇点T连边(x-a[i],0)
每个相邻仓库之间连边(inf,1)

void add(int u,int v,int w,int c){
	e[++cnt].to=v; e[cnt].next =head[u]; e[cnt].w =w ; e[cnt].c=c; head[u]=cnt;
	e[++cnt].to=u; e[cnt].next =head[v]; e[cnt].w =0 ;e[cnt].c =-c;head[v]=cnt;
}
bool spfa(){}
int EK(){}

int main(){
	n=read(); T=N-2;
	int tot=0; 
	for(int i=1;i<=n;i++){
		a[i]=read();
		tot+=a[i];
		add(i,i<n?i+1:1,inf,1);//环形连边技巧
		add(i,i>1?i-1:n,inf,1);
	}
	tot/=n;
	for(int i=1;i<=n;i++){
		if(tot<a[i]) add(S,i,a[i]-tot,0); //这里tot已经变成平均值x了
		if(tot>a[i]) add(i,T,tot-a[i],0);
	}
	printf("%d",EK());
}

二分图最大匹配

分配问题

AcWing 2193. 分配问题

有 n 件工作要分配给 n 个人做。
第 i 个人做第 j 件工作产生的效益为 cij。
试设计一个将 n 件工作分配给 n 个人做的分配方案。
对于给定的 n 件工作和 n 个人,计算最优分配方案和最差分配方案

跟运输问题很像 不解释

void add(int u,int v,int w,int c){
	e[++cnt].to =v; e[cnt].next =head[u];e[cnt].w =w; e[cnt].c =c; head[u]=cnt;
	e[++cnt].to =u; e[cnt].next =head[v];e[cnt].w =0; e[cnt].c =-c; head[v]=cnt;
}
bool spfa(){}
int EK(){}

int main(){
	n=read(); T=N-1;
	for(int i=1;i<=n;i++){
		add(S,i,1,0);
		add(i+n,T,1,0);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int c=read();
			add(i,n+j,inf,c);
		}
	}
	printf("%d\n",EK());
	for(int i=2;i<=cnt;i+=2){
		e[i].w+=e[i^1].w;
		e[i^1].w=0;
		e[i].c=-e[i].c;
		e[i^1].c=-e[i^1].c;
	} 
	printf("%d",-EK());
}

最大权不相交路径

数字梯形问题

AcWing 2191. 数字梯形问题

给定一个由 n 行数字组成的数字梯形如下图所示。
梯形的第一行有 m 个数字。
从梯形的顶部的 m 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。
规则 1:从梯形的顶至底的 m 条路径互不相交。
规则 2:从梯形的顶至底的 m 条路径仅在数字结点处相交。
规则 3:从梯形的顶至底的 m 条路径允许在数字结点相交或边相交。

对每个点设定唯一编号,并拆点。
S向第一层点连边,最后一层点向T连边
可以相互到达的点与点之间连边
点内部连边
根据三个规则对路径的限制分别修改对点的限制。

  • 1:除了点内部的边(1,w) 其余所有边的权值(1,0)
  • 2:点内部的边和连向T的边的容量都设为inf 因为可以重复走
  • 3:继续放开对点与点之间连边的限制,除了从S出发的边之外,其余的边的容量都改为inf
void add(int u,int v,int w,int c){
	e[++cnt].to =v; e[cnt].next =head[u];e[cnt].w =w; e[cnt].c =c; head[u]=cnt;
	e[++cnt].to =u; e[cnt].next =head[v];e[cnt].w =0; e[cnt].c =-c; head[v]=cnt;
}
void cl(){
	cnt=1;
	memset(e,0,sizeof e);
	memset(head,0,sizeof head);
}
bool spfa(){}
int EK(){}

int main(){
	m=read(); n=read();
	int ct=0;
	S=++ct; T=++ct;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m+i-1;j++){
			id[i][j]=++ct;
			w[i][j]=read();
		}
	}
	cl();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m+i-1;j++){
			if(i==1) add(S,id[i][j]*2,1,0);
			if(i==n) add(id[i][j]*2+1,T,1,0);
			add(id[i][j]*2,id[i][j]*2+1,1,w[i][j]);
			add(id[i][j]*2+1,id[i+1][j]*2,1,0);
			add(id[i][j]*2+1,id[i+1][j+1]*2,1,0);
		}
	}
	printf("%d\n",EK());
	cl();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m+i-1;j++){
			if(i==1) add(S,id[i][j]*2,1,0);
			if(i==n) add(id[i][j]*2+1,T,inf,0);
			add(id[i][j]*2,id[i][j]*2+1,inf,w[i][j]);
			add(id[i][j]*2+1,id[i+1][j]*2,1,0);
			add(id[i][j]*2+1,id[i+1][j+1]*2,1,0);
		}
	}
	printf("%d\n",EK());
	
	cl();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m+i-1;j++){
			if(i==1) add(S,id[i][j]*2,1,0);
			if(i==n) add(id[i][j]*2+1,T,inf,0);
			add(id[i][j]*2,id[i][j]*2+1,inf,w[i][j]);
			add(id[i][j]*2+1,id[i+1][j]*2,inf,0);
			add(id[i][j]*2+1,id[i+1][j+1]*2,inf,0);
		}
	}
	printf("%d\n",EK());
}

费用流网格图模型

K取方格数

AcWing 382. K取方格数

在一个 N×N 的矩形网格中,每个格子里都写着一个非负整数。
可以从左上角到右下角安排 K 条路线,每一步只能往下或往右,沿途经过的格子中的整数会被取走。
若多条路线重复经过一个格子,只取一次。
求能取得的整数的和最大是多少。

最大流限制为K,求最大费用
在网格图中我们常用Get()返回的hash值来获取点的唯一编号,上一道题中采用的矩阵存储也是一种方式,但hash较为节省空间。
S向(1,1)点连边(k,0)(n,n)点向T连边(k,0)
由于方格中的每个数只能取一次,但可以重复走,所以可以在拆点,在点内部连两种边,一种是(1,c)另一种(inf,0),以此满足题意
点与点之间之间没有限制,即可直接在互相可以到达的点之间连(inf,0)

void add(int u,int v,int w,int c){
	e[++cnt].to=v; e[cnt].next=head[u]; head[u]=cnt; e[cnt].w=w; e[cnt].c=c;
	e[++cnt].to=u; e[cnt].next=head[v]; head[v]=cnt; e[cnt].w=0; e[cnt].c=-c;
}
int get(int x,int y,int t){
	return ((x-1)*n+y)*2+t;
}
bool spfa(){}
int EK(){}

int main(){
	n=read(); k=read();
	S=N-1, T=N-2;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int c=read();
			add(get(i,j,0),get(i,j,1),1,c);
			add(get(i,j,0),get(i,j,1),inf,0);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i+1<=n) add(get(i,j,1),get(i+1,j,0),inf,0);
			if(j+1<=n) add(get(i,j,1),get(i,j+1,0),inf,0);
		}
	}
	add(S,get(1,1,0),k,0);
	add(get(n,n,1),T,k,0);
	printf("%d",EK());
}
深海机器人问题

AcWing 2195. 深海机器人问题

深海资源考察探险队的潜艇将到达深海的海底进行科学考察。
潜艇内有多个深海机器人。
潜艇到达深海海底后,深海机器人将离开潜艇向预定目标移动。
深海机器人在移动中还必须沿途采集海底生物标本。
沿途生物标本由最先遇到它的深海机器人完成采集。
每条预定路径上的生物标本的价值是已知的,而且生物标本只能被采集一次
本题限定深海机器人只能从其出发位置沿着向北或向东的方向移动,而且多个深海机器人可以在同一时间占据同一位置。
给定每个深海机器人的出发位置和目标位置,以及每条网格边上生物标本的价值。
计算深海机器人的最优移动方案,使尽可能多的深海机器人到达目的地的前提下,采集到的生物标本的总价值最高

网格图 get()套路
由于每个边的权值只能取一次,同上一题一样,建两种边,不过不同的是,本题的权值在边上而上一题的在点上。所以本题免去了拆点的麻烦
向每个相互可到达的点之间建好边之后,其余的就是一个多源汇的最大费用最大流问题了

void add(int u,int v,int w,int c){
	e[++cnt].to=v; e[cnt].next=head[u]; head[u]=cnt; e[cnt].w=w; e[cnt].c=c;
	e[++cnt].to=u; e[cnt].next=head[v]; head[v]=cnt; e[cnt].w=0; e[cnt].c=-c;
}

int get(int x,int y){
	return x*(m+1)+y;
}
bool spfa(){}
int EK(){}

int main(){
	int a,b;
	a=read(); b=read();
	n=read(); m=read();
	T=N-1; S=N-2;
	for(int i=0;i<=n;i++){
		for(int j=0;j<m;j++){
			int c=read();
			add(get(i,j),get(i,j+1),1,c);
			add(get(i,j),get(i,j+1),inf,0);
		}
	}
	for(int i=0;i<=m;i++){
		for(int j=0;j<n;j++){
			int c=read();
			add(get(j,i),get(j+1,i),1,c);
			add(get(j,i),get(j+1,i),inf,0);
		}
	}
	while(a--){
		int k=read(),x=read(),y=read();
		add(S,get(x,y),k,0);
	}
	while(b--){
		int k=read(),x=read(),y=read();
		add(get(x,y),T,k,0);
	}
	printf("%d",EK());
}

费用流拆点

餐巾计划问题

AcWing 2184. 餐巾计划问题

本题的建图方式比较抽象。

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

拆点,把每个点拆成出和入,也就是将每天的需求和“产出”分开。
S向每天的入点连边(inf,p) ,相当于直接购买
i-1天的出边向第i天的入边连边(inf,f)(inf,s) 相当于是送到快洗部和慢洗部
每天的脏餐巾可以留到下一天,从i-1到第i天的出点连边(inf,0)
从源点S向每天的出点连边(ri,0),对应每天用剩的餐巾是ri个
每天的出点向汇点T连边(ri,0) ,对应每天的需求是ri

void add(int u,int v,int w,int c){
	e[++cnt].to =v; e[cnt].next =head[u]; head[u]=cnt; e[cnt].c=c; e[cnt].w=w;
	e[++cnt].to =u; e[cnt].next =head[v]; head[v]=cnt; e[cnt].c=-c; e[cnt].w=0;
}
bool spfa(){}
int EK(){}
int main(){
	int p,fp,f,s,sp;
	n=read(); p=read(); f=read(); fp=read(); s=read(); sp=read();
	S=0,T=N-1;
	for(int i=1;i<=n;i++){
		int r=read();
		add(S,i,r,0);
		add(S,i+n,inf,p);
		if(i+f<=n) add(i,n+i+f,inf,fp);
		if(i+s<=n) add(i,n+i+s,inf,sp);
		if(i+1<=n) add(i,i+1,inf,0);
		add(n+i,T,r,0);
	}
	printf("%d",EK());
}

费用流上下界可行流

志愿者招募

AcWing 969. 志愿者招募
本题的建图依旧是非常抽象

申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。
布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。
经过估算,这个项目需要 N 天才能完成,其中第 i 天至少需要 Ai 个人。
布布通过了解得知,一共有 M 类志愿者可以招募。
其中第 i 类可以从第 Si 天工作到第 Ti 天,招募费用是每人 Ci 元。
新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!
于是布布找到了你,希望你帮他设计一种最优的招募方案。

将每天的志愿者需求人数下限看做流量下界,将可以从第 Si 天工作到第 Ti 天的志愿者看做是网络中的一条回流(保证流量守恒的同时可以方便地得出流量和费用)。
注意这里每一天对应的是一条从第i到第i+1个点的边,因此有1~n+1个点
那么套用上下界可行流的模板。注意上界是inf

对于新图的每个边 f ′ ( u , v ) = c u ( u , v ) − c l ( u , v ) f'(u,v)=c_u(u,v)-c_l(u,v) f(u,v)=cu(u,v)cl(u,v) 也就是上界-下界
每个点 A [   i   ] = C i n − C o u t A[\ i\ ]= C_{in} -C_{out} A[ i ]=CinCout
对于每个 A [   i   ] < 0 A[\ i\ ]<0 A[ i ]<0 的点u 建一条v–>T
对于每个 A [   i   ] > 0 A[\ i\ ]>0 A[ i ]>0 的点v 建一条S–>u

再加志愿者的回流边,即从第Ti+1个点向第Si个点连边(inf,ci)

void add(int u,int v,int w,int c){
	e[++cnt].to =v; e[cnt].next =head[u]; head[u]=cnt; e[cnt].w=w; e[cnt].c=c;
	e[++cnt].to =u; e[cnt].next =head[v]; head[v]=cnt; e[cnt].w=0; e[cnt].c=-c;
}
bool spfa(){}
int EK(){}

int main(){
	n=read(), m=read();
	T=N-1;
	int last=0,cur;
	for(int i=1;i<=n;i++){
		cur=read();
		if(cur-last<0) add(S,i,last-cur,0);
		if(cur-last>0) add(i,T,cur-last,0);
		add(i,i+1,inf-cur,0);//上界-下界
		last=cur;
	}
	add(n,n+1,inf-cur,0);//上界-下界
	add(S,n+1,cur,0); //第n+1个点只有C_in=cur 没有C_out 因此从S连
	//上面两个加边与前面的同理 注意不是特判
	for(int i=1;i<=m;i++){
		int a=read(),b=read(),s=read();
		add(b+1,a,inf,s); //志愿者的回流边
	}
	printf("%d",EK());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值