ZROI-2019.8.1

網絡流算法。
本文部分參考這篇博客
首先便是定義。
兩個特殊節點源(s)與匯(t),兩者都是有且僅有一個;弧;弧的容量c(u,v);弧上流量f(u,v);鏈;割。

弧分爲很多類型。

  • 飽和弧:f(u,v)=c(u,v)f(u,v)=c(u,v)
  • 非飽和弧:f(u,v)<c(u,v)f(u,v) \lt c(u,v)
  • 零流弧:f(u,v)=0f(u,v)=0
  • 非零流弧:f(u,v)>0f(u,v) \gt 0

鏈的定義:在容量網絡中,稱一個頂點序列爲鏈應該滿足任意相鄰兩個點之間都有弧相連,但是弧的方向不一定都要和鏈的方向相同。

割的定義:對於網絡流圖的一個子集,如果它把點分爲了兩個部分,那麼那些從一部分到另一部分的邊所組成的集合,就是割。

再是性質。

  • 容量限制:對於任意u,vVu,v \in V0f(u,v)c(u,v)0 \le f(u,v) \le c(u,v)
  • 流量守恆:對任意非S和T節點uu,有f(u,v)=0\sum f(u,v) =0

關於流的定義。

  • 可行流:滿足流量限制和平衡條件的流是可行流。
  • 零流:如果網絡流上每條弧的流量都爲0,就被成爲零流。
  • 僞流:即只滿足流量限制的流。該流對預流推進算法有作用。
  • 最大流:流量最大的可行流。

再是定理。
最大流最小割定理:最大流=最小割。由此可以得知最大流小於等於任意割的容量。

接下來我們就要看看這最大流應該怎麼求呢?

增廣路:如果圖中的一條鏈滿足以下要求:鏈中所有前向弧(與鏈方向相同的弧)都是非飽和弧,並且後向弧(與鏈方向相反的弧)都是非零的弧,那麼該鏈就是關於可行流f的一條增廣路。

如果一個可行流不是最大流,那麼當前網絡圖中一定
還存在增廣路。

增廣:沿着增廣路改進可行流的操作。

引入殘餘網絡和殘留容量。

殘留容量:
對於每一條弧上的殘留容量記cf(u,v)=c(u,v)f(u,v)cf(u,v)=c(u,v)-f(u,v),也就是這條弧上最多還能通過的流量。字面意思理解即可。當然還有個反向殘留容量cf(v,u)=f(u,v)cf(v,u)=-f(u,v)

殘餘網絡:
每找到一條源到匯的路徑後將路徑上的弧減去路徑上最小的弧的容量,反向邊上增加這個容量,得到的新圖就是原圖的殘餘網絡。

要介紹的東西沒了,下面就可以進入正題了。

費用流

費用流就是在網絡流基礎上給每條邊都加上了費用cost,該網絡的總費用就是u,vEfu,vcostu,v\sum_{u,v\in E}f_{u,v}*cost_{u,v}

下面就是喜聞樂見的算法了。

最大流算法

現在引入反向邊。
反向邊是幹嘛的呢?你每次找到一條增廣路,便直接增廣了,想想便會發現問題,如果它卡了其他的路呢?答案就變小了,這個反向邊就是給程序一個反悔的機會,而不是採用回溯,不然時間複雜度就直接上升到了一定境界。
那麼反向邊是怎麼使用的呢?我們在找到一條增廣路後,將路徑上的邊全部減去路徑上的邊的最小值,然後在這條邊的反向邊上加上這個值即可。

這裏便直接講解Dinic算法了。

Dinic算法

沒錯這便是今天的主題了。
Dinic算法將整張圖按照路徑長度分爲了若干層,那麼很顯然,找的增廣路便是滿足所有的點在不同的層。

算法過程

  • 首先用BFS將圖分層。
  • 如果發現到達不了匯點,程序就可以結束了,最大流已找到。
  • 之後便用DFS尋找增廣路了(從一層走到另一層)。
  • 在過程中如果DFS到了匯點,則找到了一條增廣路。進行增廣,但是這個時候DFS並不會結束,而是回溯,繼續找下一條路徑。
  • 再執行如上操作。

下面看看它的時間複雜度。
普通情況下,複雜度爲O(v2e)\mathcal{O}(v^2e)

一些小優化。

  • 多路增廣:每次不是尋找一條增廣路,正如優化名字那樣,形成一張增廣網。
  • 當前弧優化:記錄上一次檢查到哪條邊,如果找過,就不必再找他,找第一個沒有找過的邊。這爲什麼是對的呢?因爲每次找增廣路都會按照這條路徑最大的能力增廣,也就是說只要之前增廣過的路一定是竭其所能的。

這邊可以注意到,這些優化並沒有優化時間複雜度,但是實際情況下可以快上不少。

一些方便的細節。

我們每次都要對反向弧操作,因此我們的邊標號從0開始,這樣每次只需要異或一下即可,方便快捷。

另外一說:網絡流全部的難點在於建圖,這意味着只會背模板也是可以接受的,所以優化在一開始就加足了,是很有幫助的。

下面附上我的模板代碼QWQ,可能有點醜,能看就行了對吧。
現在還沒有添加任何優化,以後會添的QAQ。希望喜歡!
模板題目地址

#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::endl;
int S,T;
int n,m;
int head[200001],tot=1;
std::queue<int>q;
int deep[200001];
int cf[500001];
struct edge{
    int to;
    int nxt;
}e[500002];
void add(int x,int y,int z){
	e[++tot].to=y;
	e[tot].nxt=head[x];
    cf[tot]=z;
	head[x]=tot;
}
bool bfs(){
	memset(deep,0,sizeof(deep));
	while(!q.empty())
	    q.pop();
	q.push(S);
	deep[S]=1;
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int i=head[now];i;i=e[i].nxt){
			int y=e[i].to;
			if(cf[i]&&!deep[y]){
				deep[y]=deep[now]+1;
				q.push(y);
			}
		}
	}
	return deep[T];
}
int dfs(int now,int min){
	if(now==T)
	    return min;
	for(int i=head[now];i;i=e[i].nxt){
		int y=e[i].to;
		if(deep[y]==deep[now]+1&&cf[i]){
			int w=dfs(y,std::min(min,cf[i]));
			if(w){
			    cf[i]-=w;
			    cf[i^1]+=w;
			    return w;
			}
		}
	}
	return 0;
}
int Dinic(){
	int ans=0;
	while(bfs()){
		while(int w=dfs(S,0x3f3f3f3f))
		    ans+=w;
	}
	return ans;
}
main(){
	std::ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>S>>T;
	for(int i=1;i<=m;++i){
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
		add(y,x,0);
	}
	cout<<Dinic();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章