1. 程式人生 > 實用技巧 >「筆記」網路流 flows 小記

「筆記」網路流 flows 小記

基本沒有嚴謹證明。

Part. 1 概念

Part. 1-1 流網路

流網路是一個有向圖(不考慮反向邊),我們把這個圖記為 \(G=(V,E)\)

其中有兩個特殊的點 \(s,t\),分別成為源點和匯點。

對於每一個 \((u,v)\in E\),我們給它兩個屬性,一個 \(c(u,v)\),表示這條邊的容量;一個 \(f(u,v)\),表示這條邊的流量。

每一條邊都需要滿足兩個性質:

  • 容量限制,\(0\le f(u,v)\le c(u,v)\)
  • 流量守恆,\(\sum_{(x,u)\in E}f(x,u)=\sum_{u,y\in E}f(u,y)\),即流進來多少流出去多少。

(這裡的定義是 yxc / 算導 的定義,和網路上大部分的資料不同)

我們稱一個網路的可行流的流量為 \(\left(\sum_{(s,x)\in E}f(s,x)\right)-\left(\sum_{(y,s)\in E}f(y,s)\right)\),即流出源點的流量減去流入源點的流量。

一個可行流的方案我們記為 \(f\),其流量記為 \(|f|\)

Part. 1-2 殘量網路

殘量網路即把原網路的反向邊算進去,把反向邊的邊集記為 \(E'\),把殘量網路表示為 \(G_{f}=(V,E\cup E')\),把殘量網路的一個可行流方案記為 \(f'\),同理 \(|f'|\)\(c_{f}(u,v)\)\(f'(u,v)\)

這裡定義 \(f\)

之間的加法:

首先我們知道對於 \(G\) 的每一個不同的 \(f\) 而言,其 \(G'\) 是不同的。

我們定義 \(c'(u,v)=\begin{cases}c(u,v)-f(u,v),(u,v)\in E \\ \displaystyle f(v,u),(u,v)\in E'\end{cases}\)

那麼 \(f\) 之間的加法是這樣定義的:\(f'(u,v)=f'(u,v)+f(u,v),f'(v,u)=f'(v,u)-f(u,v)\)。(定義不太清楚,自行意會吧)

然後可以證明 \(|f+f'|=|f|+|f'|\),注意 \(|f'|\) 可能為負。

Part. 1-3 增廣路

增廣路就是一條從 \(s\)\(t\) 的簡單路徑,滿足路上每條邊的容量都 \(>0\)

Part. 1-4 割

割是對 \(V\) 進行的劃分。

首先我們劃分出兩個集合 \(S,T\),需滿足 \(s\in S,t\in T\)\(S\cup T=V,S\cap T=\empty\)

我們再定義割的容量:\(c(S,T)=\sum_{u\in S}\sum_{v\in T}c(u,v)\)(不考慮反向邊)。

割的流量:\(f(S,T)=\left(\sum_{u\in S}\sum_{v\in T}f(u,v)\right)-\left(\sum_{u\in S}\sum_{v\in T}f(v,u)\right)\)

這裡流量和容量的定義不對稱,不過 不 影 響。

可以證明對於任意可行流的任意割,有 \(f(S,T)\le c(S,T)\),也有 \(|f|=f(S,T)\le c(S,T)\)

最小割指容量最小的割。

Part. 1-5 最大流最小割定理

  • 可行流 \(f\) 為最大流
  • 對於可行流 \(f\),其 \(G_{f}\) 不存在增廣路。
  • 存在割 \([S,T]\)\(|f|=c(S,T)\)

以上三條可以互相推出。

最大流等於最小割。

Part. 2 演算法

Part. 1-1 Edmond-Karp

不會

Part. 1-2 Dinic

先講講暴力 FF。

算了懶得講了自己看洛谷題解的暴力程式碼吧。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 10010, E = 200010;

int n, m, s, t;
LL first[N];
LL to[E], nxt[E], val[E]/*殘餘容量*/;
int cnt = 1;
//cnt初值1,第一條邊的標號為2(二進位制10),第二條是3(二進位制11) 
//有啥好處呢?
//我們加入一條邊時,緊接著加入它的反向邊(初始容量0) 
//這兩條邊的標號就是二進位制最後一位不相同,一個0、一個1
//所以要召喚 p 這條邊的反向邊,只需用 p ^ 1
//如果cnt初值為0,就做不到。當然初值-1也可以,略需改動

//關於圖中真正的反向邊,可能引起顧慮,應該讓它們標號相鄰? 
//其實不用。該找到的增廣路都會找到的 

bool vis[N];//限制增廣路不要重複走點,否則很容易爆棧 
//兜一大圈走到匯點,還不如直接走到匯點

void addE(int u, int v, LL w) {
	++cnt;
	to[cnt] = v;
	val[cnt] = w;
	nxt[cnt] = first[u];
	first[u] = cnt;
}
LL dfs(int u, LL flow) {
	//注意,在走到匯點之前,無法得知這次的流量到底有多少 
	if (u == t)
		return flow;//走到匯點才return一個實實在在的流量 
	
	vis[u] = true;
	for (int p = first[u]; p; p = nxt[p]) {
		int v = to[p];
		if (val[p] == 0 or vis[v])//無殘量,走了也沒用 
			continue;
		int res = 0;
		if ((res = dfs(v, min(flow, val[p]))) > 0) {
			//↑順著流過去,要受一路上最小容量的限制
			val[p] -= res;//此邊殘餘容量減小
			val[p ^ 1] += res;//以後可以順著反向邊收回這些容量,前提是對方有人了 
			return res;
		}
	}
	return 0;//我與終點根本不連通(依照殘量網路),上一個點不要信任我
}
int main() {
	scanf("%d %d %d %d", &n, &m, &s, &t);
	for (int i = 1; i <= m; ++i) {
		int u, v; LL w;
		scanf("%d %d %lld", &u, &v, &w);
		addE(u, v, w);
		addE(v, u, 0);//和正向邊標號相鄰
		//反向邊開始容量為0,表示不允許平白無故走反向邊
		//只有正向邊流量過來以後,才提供返還流量的機會
	}
	LL res = 0, tot = 0;
	while (memset(vis, 0, sizeof(vis)) and (res = dfs(s, 1e18/*水庫無限*/)) > 0)
		tot += res;//進行若干回合的增廣

	printf("%lld\n", tot);
	return 0;
}

接下來講講如何 Dinic。

其實我不理解 Dinic 比暴力優在哪裡

/* okay | there's been */

#include <cstdio>

namespace mySpace {
typedef long long LL;

const int MAXN = 200 + 5, MAXM = 5000 + 5;

int rint () {
	int x = 0, f = 1; char c = getchar ();
	for ( ; c < '0' || c > '9'; c = getchar () )	f = c == '-' ? -1 : f;
	for ( ; c >= '0' && c <= '9'; c = getchar () )	x = ( x << 3 ) + ( x << 1 ) + ( c & 15 );
	return x * f;
}

template<typename _T> _T MIN ( const _T x, const _T y ) { return x < y ? x : y; }

struct GraphSet {
	int to, nx;
	LL wt;
	GraphSet () : to ( 0 ), nx ( 0 ), wt ( 0 ) {}
	GraphSet ( const int a, const int b, const LL c ) : to ( a ), nx ( b ), wt ( c ) {}
} as[MAXM * 2];

int n, m, s, t, bgn[MAXN], cnt = 1, lav[MAXN], ali[MAXN];

void makeEdge ( const int u, const int v, const LL w ) { as[++ cnt] = GraphSet ( v, bgn[u], w ), bgn[u] = cnt; }

bool bfs () {
	for ( int i = 1; i <= n; ++ i )	lav[i] = 0;
	int nowl = 1, nowr = 1;
	ali[1] = s, lav[s] = 1;
	for ( ; nowl <= nowr; ) {
		int u = ali[nowl ++];
		for ( int i = bgn[u]; i; i = as[i].nx ) {
			int v = as[i].to; LL w = as[i].wt;
			if ( ! w || lav[v] )	continue;
			lav[v] = lav[u] + 1, ali[++ nowr] = v;
		}
	}
	return lav[t];
}

LL dfs ( const int u, LL in ) {
	if ( u == t )	return in;
	LL out = 0;
	for ( int i = bgn[u]; i; i = as[i].nx ) {
		if ( ! in )	break;
		int v = as[i].to; LL w = as[i].wt;
		if ( ! w || lav[v] != lav[u] + 1 )	continue;
		LL ret = dfs ( v, MIN ( in, w ) );
		as[i].wt -= ret, as[i ^ 1].wt += ret;
		in -= ret, out += ret;
	}
	if ( ! out )	lav[u] = 0;
	return out;
}

LL calcMXflow () {
	LL res = 0;
	for ( ; bfs (); res += dfs ( s, 1e18 ) ) ;
	return res;
}

void main () {
	n = rint (), m = rint (), s = rint (), t = rint ();
	for ( int i = 1; i <= m; ++ i ) {
		int u = rint (), v = rint (); LL w = rint ();
		makeEdge ( u, v, w ), makeEdge ( v, u, 0 );
	}
	printf ( "%lld\n", calcMXflow () );
}
}

int main () {
	mySpace :: main ();
	return 0;
}