2020.05.04日常总结——最小费用最大流略讲

最小费用最大流略讲 \color{green}{\texttt{最小费用最大流略讲}} 最小费用最大流略讲

【定义】: \color{blue}{\texttt{【定义】:}} 【定义】:

  • 单边费用:费用是图中的边的又一个边权。我们记为 b u , v b_{u,v} bu,v,它表示该边流过的单位流量的花费。什么叫单位流量的花费?即记流量为 f u , v f_{u,v} fu,v,则该边上的费用为 f u , v × b u , v f_{u,v} \times b_{u,v} fu,v×bu,v

  • 全图费用:整个图的费用就是所有边的费用的总和,即( E \texttt{E} E 为边集):

    ∑ u = 1 n ∑ v = 1 n f u , v × b u , v [ u ≠ v , ( u , v ) ∈ E ] \sum\limits_{u=1}^{n} \sum\limits_{v=1}^{n} f_{u,v} \times b_{u,v} [u \neq v,(u,v) \in \texttt{E}] u=1nv=1nfu,v×bu,v[u=v,(u,v)E]

  • 算法目标:第一目标是让总流量最大,第二目标是让总花费最小。换成人话就是:在总流量最大的前提下让总花费最小。

【算法】: \color{blue}{\texttt{【算法】:}} 【算法】:

如何解决上面所述的问题呢?

仔细思考一下我们是如何进行 Dinic \texttt{Dinic} Dinic 算法的。我们使用了一个 bfs 算法算出每个点到起点的距离 dep \text{dep} dep。然后我们根据 dep \text{dep} dep 来保证我们增广的是最短路。

现在,我们只要把 bfs 算法换成 spfa 算法就可以了。我们只需把 b b b 作为花费跑一遍 spfa 算法,然后就做完了。


代码精讲 \color{green}{\texttt{代码精讲}} 代码精讲

  • 首先,是整个算法最最重要的部分——spfa 算法。

    在这里插入图片描述

  • 然后是如何更新答案?也很简单,我们在上面的代码中维护了两个非常重要的数组—— p r e pre pre p a t h path path 数组。它们是什么意思呢? p r e u pre_u preu 表示在我们求出的最短路中, u u u 点是从 p r e u pre_u preu 这个点转移过来的。而 p a t h u path_u pathu 表示从 p r e u pre_u preu 点顺着边 p a t h u path_u pathu 转移到点 u u u

    我们考虑一下如何利用它们两个更新我们的数据。很简单,我们先从 t t t 顺着 p r e pre pre 一步步爬回 s s s,在爬的过程中,我们就可以求出本条路径可以容纳的最大流量 d d d。然后,我们让总流量 flow \texttt{flow} flow 加上 d d d,同时让 cost \texttt{cost} cost 加上 d i s t × d dis_t \times d dist×d。为什么是加上 d i s t × d dis_t \times d dist×d?因为 d i s t dis_t dist 就表示这条路径上的花费总和。因为 d d d 代表这条路径中所有的边都通过 d d d 的流量,所以总花费就增加了 d i s t × d dis_t \times d dist×d.

    可能有人有这么一个问题:如何求 d d d?很简单,这条路径上所有的边的容量的最小值便是 d d d 的值。

    在这里插入图片描述

  • 最后一个问题:如何求出最终的答案。很简单,反复做如下事情:

    1. 调用 spfa_init() 函数求最短路。
    2. 调用 updata() 函数更新数据。
    3. spfa_init() 返回 false 时停止,否则回 1 反复。

    在这里插入图片描述


最后,给一道模板题(洛谷 P4016)的代码给大家:

const int N=110,M=420;
struct edge{
	int next,to,dis,cost;
}e[M<<1];int h[N],tot=1;
void add(int a,int b,int c,int d){
	e[++tot]=(edge){h[a],b,c,d};h[a]=tot;
	e[++tot]=(edge){h[b],a,0,-d};h[b]=tot;
}
int dis[N],pre[N],path[N];bool vis[N];
inline bool spfa_find(int s,int t){
	memset(pre,-1,sizeof(pre));
	memset(dis,127,sizeof(dis));
	memset(vis,true,sizeof(vis));
	queue<int> q;q.push(s);dis[s]=0;
	while (!q.empty()){//开始spfa算法啦 
		int u=q.front();q.pop();vis[u]=true;
		for(int i=h[u];i;i=e[i].next)
			if (e[i].dis>0){//有更新的意义 
				register int to=e[i].to;
				if (dis[to]>dis[u]+e[i].cost){
					dis[to]=dis[u]+e[i].cost;
					pre[to]=u;//从点u转移到to 
					path[to]=i;//转移到to的边
					if (vis[to]){
						q.push(to);
						vis[to]=false;
					}
				}
			}
	}//spfa求以cost为边权的图的最短路 
	return pre[t]!=-1;//可以做到终点 
}
int ans_cost,ans_flow,n,s,t,a[N],m;
inline void updata(int s,int t){
	register int d=0x3f3f3f3f;
	for(int i=t;i!=s;i=pre[i])
		d=min(d,e[path[i]].dis);
//	求出我们本次可以获得的最大流量 
	ans_cost+=d*dis[t];//更新花费 
	ans_flow+=d;       //更新流量 
	for(int i=t;i!=s;i=pre[i]){
		e[path[i]].dis-=d;
		e[path[i]^1].dis+=d;
	}//更新边权(重要勿忘) 
}
inline void calc_mincost_maxflow(){
	while (spfa_find(s,t)) updata(s,t);
}//求最小费用最大流(函数名通过直译得) 
int main(){
	freopen("t1.in","r",stdin);
	scanf("%d",&n);s=n+1;t=n+2;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		m+=a[i];//m:总和 
	}
	for(int i=1;i<=n;i++)
		a[i]-=m/n;//减去平均数 
	for(int i=1;i<=n;i++){
		if (a[i]>0) add(s,i,a[i],0);
		if (a[i]<0) add(i,t,-a[i],0);
	}
	for(int i=1;i<n;i++){
		add(i+1,i,0x3f3f3f3f,1);
		add(i,i+1,0x3f3f3f3f,1);
	}//注意需要添加双向边 
	add(n,1,0x3f3f3f3f,1);
	add(1,n,0x3f3f3f3f,1);
	calc_mincost_maxflow();
	printf("%d",ans_cost);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值