2020.04.25日常總結——網絡流入門第一記

爲紀念自己花了三天入門了網絡流,特發此博客。


網絡流\color{green}{\text{網絡流}}

理解一個東西,首先我們得知道它是什麼?什麼是網絡流?先說幾個概念:

  • 容量:相當於普通圖中的邊權。特別的,它表示的是經過此邊的東西的量的限制。記爲 cu,vc_{u,v}
  • 流量:即有多少東西經過這裏。記爲 fu,vf_{u,v}

所謂的網絡流,就是一個邊有容量限制的圖。它具有兩個非常特殊的點:源點 ss 和匯點 tt,可以簡單的理解爲 bfs 的起點和終點。它應該滿足以下幾個條件:

  • 容量限制:對於每條邊,流經該邊的流量不得超過該邊的容量。即 fu,vcu,vf_{u,v} \leq c_{u,v}
  • 斜對稱性:每條邊的流量與其相反邊的流量之和爲 00,即 fu,v+fv,u=0fu,v=fv,uf_{u,v} + f_{v,u} = 0 \to f_{u,v} = -f_{v,u}
  • 流守恆性:從源點流出的流量等於匯點流入的流量,即 (s,x)Efs,x=(x,t)Efx,t\sum\limits_{(s,x) \in \text{E}} f_{s,x} = \sum\limits_{(x,t) \in \text{E}} f_{x,t}EE 表示邊集。

最大流\color{green}{\text{最大流}}

最大流是網絡流中的第一課(也就是我學了三天的才入門的那個)。它的目標是從 ss 出發給到 tt 最大的流量。我們約定除了 s,ts,t 外的其它點需要滿足:流入它的流量和等於從它流出的流量和。

形象地理解一下:ss 就是一個自來水場,tt 是你家。由於你沒充錢,所以從自來水場流出的水必須先經過幾箇中間節點才能到你家。每天自來水管有水量的限制(多了會爆水管),中間點不能“創造”和“消耗”水(即上一段的約定),問你最多可以同時收到多少水?

如何解決?有很多種算法。最簡單的算法是利用殘餘網絡。什麼是殘餘網絡?很簡單,就是點集和邊集和原圖一樣,但是邊權爲 cu,vfu,vc_{u,v}-f_{u,v} 的圖。

我們記 su1u2unts \to u_1 \to u_2 \to \cdots \to u_n \to t(其中 (s,u1),(ui,ui+1),(un,t)(s,u_1),(u_i,u_{i+1}),(u_n,t) 都是原圖中的邊)爲一條路徑。如果這條路徑中邊權的最小值 0\geq 0,就是增廣路。

增廣路有什麼作用?很簡單,有增廣路了就沒有求出最大流。爲什麼?因爲我們再沿着增廣路走,還可以獲得更多的水。

於是,我們每次求出增廣路更新答案就可以了!!!

但是,這樣是有反例的( x/yx/y 表示流量爲 xx,容量爲 yy):

在這裏插入圖片描述

如圖,如果我們走 12341 \to 2 \to 3 \to 4,最後的答案爲 22。但是如果我們走兩條 124+1341 \to 2 \to 4 + 1 \to 3 \to 4,答案爲 44

原因在於,我們沒有給程序一個反悔的機會。也就是說,程序執行了這一步之後,就再沒有撤銷的機會了。怎麼辦呢?

很簡單,我們給圖加上一個反向邊即可。反向邊的邊權就是正向邊流過的流量。比如這樣:

在這裏插入圖片描述

這樣,我們就可以算到正確的答案 44 了。我們直接按照這種方法就可以獲得正確的答案。如何找增廣路?用 dfsbfs 就可以啦。時間複雜度爲 O(n×m2)O(n \times m^2)nn 爲點數,mm 爲邊數,下同。


Dinic算法\color{green}{\texttt{Dinic}\text{算法}}

如何優化它?我們發現,對於這樣的圖,我們的算法就很慢:

在這裏插入圖片描述

原本,直接走 1241 \to 2 \to 4 就可以一次獲得 1×10201 \times 10 ^{20} 的答案,但是程序偏偏選擇了最慢的路徑走,導致超時。

有沒有什麼方法?一個顯然的結論是經過的邊越多,對答案的貢獻越差(至少不優)。所以,我們選擇最短的路來增廣。

更進一步地,怎麼樣才能使路最短。假設 did_i 表示 iiss 的距離(即經過的邊數),一條路徑 su1u2uvts \to u_1 \to u_2 \to \cdots \to u_v \to t 是最短的必然有 dui+1=dui+1,du1=1,dt=duv+1d_{u_{i+1}} = d_{u_i} + 1,d_{u_1} =1,d_t = d_{u_v} + 1

於是我們可以提前用 bfs 算出所有的 dd,然後在 dfs 時加上這個限制。加入了這個限制之後的算法就是 Dinic\texttt{Dinic} 算法。可以證明,Dinic 算法的時間複雜度頂多是 O(n2×m)O(n^2 \times m)

還有一個優化叫做當前弧優化:很簡單,就是我們遍歷過邊 (i,j)(i,j) 後,下次不再走了。另一個不知名的優化是:當 dfs 遍歷過一個點時,不再遍歷它。


最激動人心的時刻——代碼\color{green}{\text{最激動人心的時刻——代碼}}

  • 在這裏插入圖片描述
    這一個代碼表示鏈式前向星的建圖和加邊。沒什麼好說的,就是注意加反向邊且反向邊的初始邊權爲 00

  • 在這裏插入圖片描述
    這段代碼就是 bfs 的分層。depdep 數組就是 ddcurcur 就是 ww。也沒什麼好講的。

  • 在這裏插入圖片描述
    這段代碼就是 dfs 的計算增廣路。distdist 表示可以給到 uu 點的最大流量(可以不用完),flowflow 就是實際可以計算到的最大流量。注意遞歸調用(d=dfs(to,min(dist,e[i].dis)))那裏,因爲邊 ii 的流量限制是 e[i].dis,所以給到 toto 的流量不能超過 e[i].dis(否則就爆水管了)。再略微注意一下 e[i^1].dis+=d,這個就是對反向邊的處理。因爲我們用 i^1 表示邊 ii 的反向邊,所以加邊的時候必須從偶數(通常爲 0022)開始給邊編號。提一下,for 那裏就是當前弧優化,ii 前面那個 & 不能少。

  • 在這裏插入圖片描述
    這就是 dinic 算法的主程序,也沒什麼好講 (大不了背,反正這麼短) ,注意 curcur 的初始化即可。

最後,把所有的代碼打在一起就是完整的代碼啦,給一道模板題(洛谷 P3376)的代碼給大家。

const int N=1e4+100;
const int M=1e5+100;
struct edge{
	int next,to,dis;
}e[M<<1];int h[N],tot=1;
//注意,要用i^1表示邊i的反向邊 
//則第1條邊的標號必須是偶數(2/0) 
inline void add(int a,int b,int c){
	e[++tot]=(edge){h[a],b,c};h[a]=tot;
}
int dep[N],cur[N],n,m,s,t,ans;
inline bool bfs(){//給圖分層 
	memset(dep,0,sizeof(dep));
	queue<int> q;q.push(s);dep[s]=1;
	while (!q.empty()){
		int i,u=q.front();q.pop();
		for(i=h[u];i;i=e[i].next){
			register int to=e[i].to;
			if (!dep[to]&&e[i].dis>0){
				dep[to]=dep[u]+1;//計算深度 
				q.push(to);//第一次到,入隊 
			}
		}
	}
	return dep[t]!=0;
}
int dfs(int u,int dist){//找增廣路 
	if (u==t) return dist;//到達終點 
	register int flow=0;//實際用流量 
	for(int &i=cur[u];i;i=e[i].next)
		if (e[i].dis>0){//有找下去的意義 
			register int to=e[i].to,d;
			if (dep[to]==dep[u]+1){
				if ((d=dfs(to,min(e[i].dis,dist)))>0){
					flow+=d;dist-=d;e[i].dis-=d;e[i^1].dis+=d;
					if (dist==0) return flow;//流量耗盡,直接返回 
				}
			}
		}
	return flow;//實際只能增加這麼多的流量 
}
const int inf=0x3f3f3f3f;
inline int dinic_algorithm(){
	while (bfs()){
		for(int i=1;i<=n;i++)
			cur[i]=h[i];
		register int d=0;
		while (d=dfs(s,inf))
			ans+=d;
	}
	return ans;
}
int main(){
	n=read();m=read();s=read();t=read();
	for(int i=1,u,v,w;i<=m;i++){
		u=read();v=read();w=read();
		add(u,v,w);add(v,u,0);
	}
	cout<<dinic_algorithm();
	return 0;
}

當然,dinic 算法通常是跑不到 O(n2×m)O(n^2 \times m) 的,它跟 spfa 一樣不穩定。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章