2020.05.04日常總結——最小費用最大流略講

最小費用最大流略講\color{green}{\texttt{最小費用最大流略講}}

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

  • 單邊費用:費用是圖中的邊的又一個邊權。我們記爲 bu,vb_{u,v},它表示該邊流過的單位流量的花費。什麼叫單位流量的花費?即記流量爲 fu,vf_{u,v},則該邊上的費用爲 fu,v×bu,vf_{u,v} \times b_{u,v}

  • 全圖費用:整個圖的費用就是所有邊的費用的總和,即(E\texttt{E} 爲邊集):

    u=1nv=1nfu,v×bu,v[uv,(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}]

  • 算法目標:第一目標是讓總流量最大,第二目標是讓總花費最小。換成人話就是:在總流量最大的前提下讓總花費最小。

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

如何解決上面所述的問題呢?

仔細思考一下我們是如何進行 Dinic\texttt{Dinic} 算法的。我們使用了一個 bfs 算法算出每個點到起點的距離 dep\text{dep}。然後我們根據 dep\text{dep} 來保證我們增廣的是最短路。

現在,我們只要把 bfs 算法換成 spfa 算法就可以了。我們只需把 bb 作爲花費跑一遍 spfa 算法,然後就做完了。


代碼精講\color{green}{\texttt{代碼精講}}

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

    在這裏插入圖片描述

  • 然後是如何更新答案?也很簡單,我們在上面的代碼中維護了兩個非常重要的數組——preprepathpath 數組。它們是什麼意思呢?preupre_u 表示在我們求出的最短路中,uu 點是從 preupre_u 這個點轉移過來的。而 pathupath_u 表示從 preupre_u 點順着邊 pathupath_u 轉移到點 uu

    我們考慮一下如何利用它們兩個更新我們的數據。很簡單,我們先從 tt 順着 prepre 一步步爬回 ss,在爬的過程中,我們就可以求出本條路徑可以容納的最大流量 dd。然後,我們讓總流量 flow\texttt{flow} 加上 dd,同時讓 cost\texttt{cost} 加上 dist×ddis_t \times d。爲什麼是加上 dist×ddis_t \times d?因爲 distdis_t 就表示這條路徑上的花費總和。因爲 dd 代表這條路徑中所有的邊都通過 dd 的流量,所以總花費就增加了 dist×ddis_t \times d.

    可能有人有這麼一個問題:如何求 dd?很簡單,這條路徑上所有的邊的容量的最小值便是 dd 的值。

    在這裏插入圖片描述

  • 最後一個問題:如何求出最終的答案。很簡單,反覆做如下事情:

    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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章