Try   HackMD

【模板】網路流(Flow)

網路流問題

給定一張圖,每條邊有容量,給定源點、匯點,求從源點到匯點的最大流量。

上述為一個最大流問題。在一個網路流問題中,我們定義:

  • 源點為
    s
    ,匯點為
    t
  • F(u,v)
    u
    v
    的流量
  • C(u,v)
    u
    v
    的容量

而一個可行流,即是對於所有邊

u,v 找到一個流量
F(u,v)
滿足:

  • 對一個節點
    u
    ,流進 = 流出:
    uvinF(v,u)=voutF(u,v)
  • 流量不超過容量:
    F(u,v)C(u,v)

Ford–Fulkerson Algorithm(最大流演算法)

要找最大流,我們可以不斷找一條尚未飽和的路徑,將其填充流量,直到找不到為止。但這樣會造成「路徑阻塞的問題」。考慮以下網路:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

由於直接找路徑填充流量會造成阻塞,我們還需要「回推」錯誤的流。回推一條邊的流

F(u,v),也是幫
v
找到新的流量來源,幫
u
找到新的流量,使得在移除
u,v
的流量時,還能維持
u,v
的進出總量不變。

建構一個剩餘網路: 對於一個可行流,我們使

R(u,v)=C(u,v)F(u,v)
R(v,u)=F(u,v)
。直白的解釋,對於一條邊的容量,我們在剩餘網路蓋一條正向邊,表示剩餘的容量;再蓋一條反向邊,代表已經使用的容量,因此
R(u,v)+R(v,u)=C(u,v)

假設有

F(u,v)=2,C(u,v)=6,則
R(u,v)=4,R(v,u)=2

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

若要對

uv 「填充」或「移除」流量
2
,則在剩餘網路上會如此表現:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

因此我們可以在剩餘網路上找一條

st 路徑,其邊權皆至少為
d
,然後把路徑上的邊都「填充」或「移除」流量
d
,那我們就成功使最大流增加
d
了。此路徑稱為增廣路徑。通常我們會使
d
盡量大,使
d
為這條路的瓶頸。

增廣路徑最大流定理:若圖是簡單的,且所有容量為非負整數:
若有可行流

F 使得找不到增廣路徑
F
為最大流

則根據上述定理,不斷找出增廣路徑,就能找到最大流。而每次找到的增廣路流量至少為

1,因此複雜度為
O(VF)

找增廣路時使用 BFS,使得增廣路總是

st 最短路,則為 Edmond-Karps 算法,複雜度
O(E2V)
證明參考

Dicnic

Dicnic 算法思想:

因為要找可行的增廣路徑,因此剩餘網路

Gr,只保留那些
R(u,v)>0
的邊。接著從源點
s
BFS 得到每個點
u
的最短距離
lev[u]
並依此做分層,使得節點
u
只能連向
lev[v]=lev[u]+1
的節點
v
。稱分層後的圖
GL
為「層次圖」。

在層次圖

GL 上找到一個最大增廣流(可以有分支)
fb
,稱為「阻塞流」。

Dicnic 算法流程如下:

  1. Gr
    上 BFS 出層次圖
    GL
    (途中要略過
    R(u,v)=0
    的邊)
  2. 在層次圖上找到阻塞流
    fb
  3. fb
    併入原先的流
    f
    ,即
    ff+fb
  4. 重複直到在
    Gr
    st
    不連通(
    Gr
    上的任一條
    st
    路徑使得瓶頸
    >0
    都是增廣路)

在 Dicnic 結束後,不存在

st 路徑,不管怎麼走都會遇到一條邊
(u,v)
使得
R(u,v)=0
。其意義為流量已經流滿。在剩餘網路上的特徵,是源點可以走到
u
但不可走到
v
。這條邊即為割邊。

Dicnic 的複雜度為

O(V2E)。在邊權皆為一的圖上,大約是
O(VE×min(V2/3,E1/2))
。做二分圖匹配時,複雜度為
O(EV)

/**
 * Description: Fast flow. After computing flow, edges $\{u,v\}$ such that 
	* $lev[u] \neq -1,$ $lev[v] = -1$ are part of min cut.
	* Use \texttt{reset} and \texttt{rcap} for Gomory-Hu.
 * Time: $O(N^2M)$ flow, $O(M\sqrt N)$ bipartite matching, $O(NM\sqrt N) or $O(NM\sqrtM) on unit graph.
 */
struct Dinic {
	using F = ll; // flow type
	struct Edge { int to; F flo, cap; };
	int N; V<Edge> eds; V<vi> adj;
	void init(int _N) { N = _N; adj.rsz(N), cur.rsz(N); }
	/// void reset() { each(e,eds) e.flo = 0; }
	void ae(int u, int v, F cap, F rcap = 0) { assert(min(cap,rcap) >= 0); 
		adj[u].pb(sz(eds)); eds.pb({v,0,cap});
		adj[v].pb(sz(eds)); eds.pb({u,0,rcap});
	}
	vi lev; V<vi::iterator> cur;
	bool bfs(int s, int t) { // level = shortest distance from source
		lev = vi(N,-1); F0R(i,0,N) cur[i] = begin(adj[i]);
		queue<int> q({s}); lev[s] = 0; 
		while (sz(q)) { int u = q.front(); q.pop();
			for (auto &e: adj[u]) { const Edge& E = eds[e];
				int v = E.to; if (lev[v] < 0 && E.flo < E.cap) 
					q.push(v), lev[v] = lev[u]+1;
			}
		}
		return lev[t] >= 0;
	}
	F dfs(int v, int t, F flo) {
		if (v == t) return flo;
		for (; cur[v] != end(adj[v]); cur[v]++) {
			Edge& E = eds[*cur[v]];
			if (lev[E.to]!=lev[v]+1||E.flo==E.cap) continue;
			F df = dfs(E.to,t,min(flo,E.cap-E.flo));
			if (df) { E.flo += df; eds[*cur[v]^1].flo -= df;
				return df; } // saturated >=1 one edge
		}
		return 0;
	}
	F maxFlow(int s, int t) {
		F tot = 0; while (bfs(s,t)) while (F df = 
			dfs(s,t,numeric_limits<F>::max())) tot += df;
		return tot;
	}
};

signed main() {
    roadroller
}

費用流

若流量有每單位收費,即邊

(u,v) 有流量
f(u,v)
則必須收取
f(u,v)×w(u,v)
,則為費用流。費用流的思想是在每次尋找增廣路徑時,總是找花費最低的。做 Edmond-Kaprs 或 Dicnic 時改成最短路即可。

struct MCMF {
	using F = ll; using C = ll; // flow type, cost type
	struct Edge { int to; F flo, cap; C cost; };
	int N; V<C> p, dist; vi pre; V<Edge> eds; V<vi> adj;
	void init(int _N) { N = _N; // vertex: [0, _N)
		p.rsz(N), dist.rsz(N), pre.rsz(N), adj.rsz(N); }
	void add_edge(int u, int v, F cap, C cost) {
		assert(cap >= 0); assert(0 <= u && u <= _N); assert(0<=v && v <= _N);
		adj[u].pb(sz(eds)); eds.pb({v,0,cap,cost});
		adj[v].pb(sz(eds)); eds.pb({u,0,0,-cost});
	} // use asserts, don't try smth dumb
	bool path(int s, int t) { // find lowest cost path to send flow through
		const C inf = numeric_limits<C>::max(); F0R(i,0,N) dist[i] = inf;
		using T = pair<C,int>; priority_queue<T,vector<T>,greater<T>> todo;
		todo.push({dist[s] = 0,s});
		while (sz(todo)) { // Dijkstra
			T x = todo.top(); todo.pop(); if (x.ff > dist[x.ss]) continue;
			for (auto &e: adj[x.ss]){ const Edge& E = eds[e]; // all weights should be non-negative
				if (E.flo < E.cap && cmin(dist[E.to],x.ff+E.cost+p[x.ss]-p[E.to]))
					pre[E.to] = e, todo.push({dist[E.to],E.to});
			}
		} // if costs are doubles, add some EPS so you
		// don't traverse ~0-weight cycle repeatedly
		return dist[t] != inf; // return flow
	}
	pair<F,C> calc(int s, int t) { assert(s != t);
		F0R(_,0,N) F0R(e,0,sz(eds)) { const Edge& E = eds[e]; // Bellman-Ford
			if (E.cap) cmin(p[E.to],p[eds[e^1].to]+E.cost); }
		F totFlow = 0; C totCost = 0;
		while (path(s,t)) { // p -> potentials for Dijkstra
			F0R(i,0,N) p[i] += dist[i]; // don't matter for unreachable nodes
			F df = numeric_limits<F>::max();
			for (int x = t; x != s; x = eds[pre[x]^1].to) {
				const Edge& E = eds[pre[x]]; cmin(df,E.cap-E.flo); }
			totFlow += df; totCost += (p[t]-p[s])*df;
			for (int x = t; x != s; x = eds[pre[x]^1].to)
				eds[pre[x]].flo += df, eds[pre[x]^1].flo -= df;
		} // get max flow you can send along path
		return {totFlow,totCost};
	}
};

網路流應用

最大流最小割

st 割:把節點劃分為兩個集合
S,T
,其中
sS,tT
,便是割。若邊
(u,v)
橫跨兩個集合,則屬於該割的割集
XC
。而
st
割的容量為所有割集中的邊的權重和,即
c=(u,v)XCw(u,v)

不嚴謹地說,割即為圖的一個斷面。畫一條線割開

s
t
使得
s,t
不連通,線經過的邊即為割集。(如果在一條邊經過經過線偶數次,那麼它不屬於割集)。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

最小割即為所有割中容量最小的,**也就是是在求

s
t
中流量最小的斷面。**容易相信,上面這個敘述跟最大流是等價,因此最大流和最小割是相同的問題。題目要求最小割集時,因為最小割集即是最大流流量瓶頸,因此在 Dicnic 時一旦找到最大流,那些
(u,v)
使得一邊可從
u
到達、一邊不可則屬於割集。總結而言,以下三件是等價:

  • f
    為最大流
  • 剩餘網路
    Gr
    不存在增廣路徑
  • |f|
    為最小割

匹配、最小覆蓋、最大獨立

名詞定義:

  • 最大邊獨立集(最大匹配):最大的邊集,使得沒有邊共用同一個點
  • 最小邊覆蓋:最小的邊集,每個點都被至少一個邊覆蓋
  • 最大點獨立集(最大獨立集):最大的點集,使得沒有點共用同一個邊(沒有點相鄰)
  • 最小點覆蓋:最小的點集,使得每個邊都至少被一個點覆蓋

要注意的是最大匹配(maximum matching)和極大匹配(maximal matching)不同,極大匹配指的是沒辦法直接加入其他邊,已經達到飽和的情況。

有以下性質:

  • 點數 = 最大邊獨立集 + 最小邊覆蓋
  • 點數 = 最大點獨立集 + 最小點覆蓋

對於二分圖還有:

  • 最大邊獨立集 = 最小點覆蓋(最小邊覆蓋 = 最大點獨立集)

而二分圖的最大匹配可以用 Dicnic 求。二分圖帶權最大匹配則可以用 MCMF 求。

DAG 的最小路徑覆蓋:

DAG 的最小「不相交」路徑覆蓋可以轉成二分圖最大匹配來求,而最小「可相交」路徑覆蓋可以轉成不相交來求。

DAG 最小不相交路徑覆蓋:
將點

x 拆成
xin,xout
,若有邊
uv
,則在新圖建邊
uoutvin
。新圖為二分圖,且最小不相交路徑覆蓋 =
|V|
新圖最大匹配。

證明:
一開始每個點都是一個路徑,每當選擇一個邊將就可以把兩個路徑首尾相連,即所需路徑數量減一。因此我們要求最大的邊集。但路徑不能相交,即一個點不能同時有一個以上的出度或入度,因此我們將入度和出度拆開,其中每個入度或出度都只能被一條邊覆蓋。因此新圖最大匹配即是路徑減少的數量。

DAG 最小相交路徑覆蓋:

若點

x 可以走到
y
,則建邊
xy

考慮

AB,BC,DB,BE。路徑可以相交,即可能會出現
ABC
DBE
想同時存在,則此時讓第二條路徑直接略過
B
,走
DE
即可。

常見網路流模型整理

最大權閉合子圖:

  • TIOJ 2128 . 殿壬發大財

拆點:

  • Luogu 1231. 教辅的组成

附註