1. 程式人生 > 其它 >圖論專題-學習筆記:ISAP 求解最大流

圖論專題-學習筆記:ISAP 求解最大流

目錄

1. 前言

本篇博文將會重點講解 ISAP 求解最大流。

ISAP 求解最大流,是目前筆者知道的 除了 HLPP 之外的速度最快的最大流演算法。

在學習 ISAP 求解最大流之前,您需要對以下知識有所瞭解,包括但不限於:網路流基礎定義,FF/EK 求解最大流的 思路,dinic 求解最大流的 程式碼實現

如果您對上述部分內容不熟悉,可以前往筆者所寫的以下博文檢視:

  1. 網路流基礎定義:演算法學習筆記:網路流#1——有關內容+演算法導航
  2. FF/EK 求解最大流的 思路演算法學習筆記:網路流#2——EK 求解最大流
  3. dinic 求解最大流的 程式碼實現演算法學習筆記:網路流#3——dinic 求解最大流

2. 模板

模板題:P3376 【模板】網路最大流

2.1 詳解

dinic 演算法已經足夠高效了,但是非常遺憾,dinic 仍然可以被卡掉,而且出現這種資料就代表 dinic 一定會 TLE,可以見最後面的資料比較。

不行,不穩定的演算法我們不要,我們需要一種更加穩定的演算法來處理最大流問題。

於是 ISAP 出現了。

ISAP 的核心思路就是:先用 一遍 BFS 從 \(t\) 開始 分層,同時記錄 \(gap_i\)

陣列表示當前層數為 \(i\) 的點的數量,然後尋找增廣路。

尋找增廣路的過程中,一旦有一個點接受到的流量不能全部流出,那麼就將這個點的層數提高 1,如果在提高完之後出現了斷層(有一層沒有點了),意味著演算法結束。

大體分為 3 個步驟:

  1. 分層
  2. 找增廣路以及提高層數
  3. 出現斷層則結束演算法

演算法好理解,但是正確性呢?

2.2 正確性證明

ISAP 的正確性證明可能需要感性理解一下。

根據上面的演算法步驟,當一個點推流推完的時候,這個點的層數不提高。

為什麼?因為此時這個點的層數已經足夠其推流了,不需要再提高,而且根據 dinic 演算法的要求,流量只能在相鄰層之間移動

那麼假設當前點推流推不出去了,這個時候需要提高層數,但是為什麼這樣就一定正確了呢?

想一個問題:如果當前的點無法推流了,那麼前面的點呢?

是不是也無法推流了呀!那麼因此如果我們提高了這個點的層數,前面的點也必須要提高層數(否則無法推流),此時就能夠保證至少這個點接受流量不變。

而如果後面的點有層次更高的點,就可以繼續推流,存在增廣路;如果沒有了,都是層次比較低的點,此時滿足以下幾個條件:

  1. 層次比較低的點推流完畢,無法再推流(即使得到 \(INF\) 的流量)。
  2. 由一,不存在增廣路,滿足演算法結束條件。
  3. 於此同時,由於後面的點層次比較低,又推流完畢,沒法提高層次,這個時候就會 出現斷層,滿足演算法結束條件。

綜上,演算法正確性成立。

而 ISAP 巧妙的地方就在於它只要做一遍 BFS,大大減少執行時間,而且因為出現斷層就是演算法結束,可以保證不會被層次大的構造的圖卡掉(具體見後面的對比)。

同樣的,ISAP 可以使用當前弧優化,參照 dinic 的當前弧優化,證明也是一樣的。

2.3 程式碼

程式碼:

/*
========= Plozia =========
	Author:Plozia
	Problem:P3376 【模板】網路最大流——ISAP 寫法
	Date:2021/3/19
========= Plozia =========
*/

#include <bits/stdc++.h>

typedef long long LL;
const int MAXN = 200 + 10, MAXM = 5000 + 10;
const LL INF = 0x7f7f7f7f7f7f7f7f;
int n, m, s, t, dep[MAXN], gap[MAXN], cnt_Edge = 1, Head[MAXM << 1], cur[MAXN];
struct node {int to; LL val; int Next;} Edge[MAXM << 1];
int q[MAXN], l, r;
LL ans;

int read()
{
	int sum = 0, fh = 1; char ch= getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
	return (fh == 1) ? sum : -sum;
}
LL Min(LL fir, LL sec) {return (fir < sec) ? fir : sec;}
void add_Edge(int x, int y, int z) {Edge[++cnt_Edge] = (node){y, (LL)z, Head[x]}; Head[x] = cnt_Edge;}

void bfs()
{
	q[l = r = 1] = t;
	memset(dep, -1, sizeof(dep)); dep[t] = 0; ++gap[0];
	//注意這地方 dep 初始化為 0 會出現一些奇奇怪怪的問題
	while (l <= r)
	{
		int x = q[l++];
		for (int i = Head[x]; i; i = Edge[i].Next)
		{
			int u = Edge[i].to;
			if (dep[u] != -1) continue ;
			dep[u] = dep[x] + 1; q[++r] = u; ++gap[dep[u]];
		}
	}
}

LL dfs(int now, LL Flow)
{
	if (now == t) return Flow;
	LL used = 0;
	for (int i = cur[now]; i; i = Edge[i].Next)
	{
		cur[now] = i; int u = Edge[i].to;
		if (Edge[i].val && dep[now] == dep[u] + 1)//注意控制層數
		{
			LL Minn = dfs(u, Min(Edge[i].val, Flow - used));
			if (Minn)
			{
				Edge[i].val -= Minn; Edge[i ^ 1].val += Minn; used += Minn;
				if (used == Flow) return used;
			}
		}
	}
	--gap[dep[now]];//提高層數
	if (gap[dep[now]] == 0) dep[s] = n + 1;//出現斷層
	++dep[now]; ++gap[dep[now]];
	return used;
}

int main()
{
	n = read(), m = read(), s = read(), t = read();
	for (int i = 1; i <= m; ++i)
	{
		int x = read(), y = read(), z = read();
		add_Edge(x, y, z); add_Edge(y, x, 0);
	}
	bfs();
	while (dep[s] <= n) {for (int i = 1; i <= n; ++i) cur[i] = Head[i]; ans += dfs(s, INF);}
	//出現斷層就結束演算法
	printf("%lld\n", ans);
	return 0;
}

3. 演算法對比

3.1 一般資料下的對比

一般資料的對比參照 luogu 的模板題提交結果。

  • 全部採用快讀
  • 全部採用 STL 容器的佇列(所以上面的 ISAP 程式碼不適用於這裡的對比)
  • 全部不開啟 \(O_2\) 優化
  • dinic 和 ISAP 採用當前弧優化
  • 全部不對重邊進行合併處理,這樣做是為了更具有可比性
  • 程式碼長度以 Dev-C++ 資料為準
  • 因為碼風問題,或許筆者所測的程式碼長度與讀者所測的程式碼長度差距很大,請讀者諒解。
  • 因為 FF 被群毆了所以沒有 FF
演算法 程式碼長度 使用時間 使用空間
EK 1.746K 605ms 892.00KB
dinic 2.071K 45ms 884.00KB
ISAP 2.078K 38ms 900.00KB

從上面的表格可以看出來:

  • EK 的碼量相對短一點,但是耗時太長了。
  • dinic 和 ISAP 其實在這些資料下沒什麼差別。

其實一般的網路流題目主要考建模能力,一般不會來卡你演算法時間(當然 EK 被卡掉比較普遍),但是萬一出題人構造資料卡你,那麼還是使用 ISAP 吧。

順便提一下,根據目前筆者所瞭解,ISAP 貌似不支援費用流問題,dinic 支援,所以 dinic 還是很重要的。

當然 ISAP 實在是太難卡了(目前筆者無法構造 hack 資料)以至於沒必要學 HLPP,HLPP 碼量又長又容易寫錯,而且必須加各種玄學優化才會比 ISAP 快,普通的 HLPP 跑不過 ISAP。

3.2 特殊資料下的對比

此處的資料引用了洛谷使用者 @離散小波變換° 在加強版模板 P4722 【模板】最大流 加強版 / 預留推進 題解裡面給出的資料,在此表示感謝。

P.S. 筆者感覺表格裡面 \(n,m\) 的資料是假的,因為好像 dinic 在這種資料下根本沒法在一秒裡面跑完,真正的資料或許是 \(n=10^4,m=3 \times 10^4\)

首先給出原文中的資料表格:

測試資料 性質 1 性質 2 性質 3 性質 4 性質 5 \(n=\) \(m=\)
1 \(10^5\) \(3 \times 10^5\)
2 \(10^5\) \(3 \times 10^5\)
3 \(10^5\) \(3 \times 10^5\)
4 \(10^5\) \(3 \times 10^5\)
5 \(10^5\) \(3 \times 10^5\)
6 \(10^5\) \(3 \times 10^5\)
7 \(10^5\) \(3 \times 10^5\)
8 \(10^5\) \(3 \times 10^5\)
9 \(10^5\) \(3 \times 10^5\)
10 \(10^5\) \(3 \times 10^5\)
11 \(10^5\) \(3 \times 10^5\)
12 \(10^5\) \(3 \times 10^5\)
13 \(10^5\) \(3 \times 10^5\)
14 \(10^5\) \(3 \times 10^5\)
15 \(10^5\) \(3 \times 10^5\)
16 \(10^5\) \(3 \times 10^5\)
17 \(10^5\) \(3 \times 10^5\)
18 \(10^5\) \(3 \times 10^5\)
19 \(10^5\) \(3 \times 10^5\)
20 \(10^5\) \(3 \times 10^5\)
21 \(10^5\) \(3 \times 10^5\)
22 \(10^5\) \(3 \times 10^5\)
23 \(10^5\) \(3 \times 10^5\)
24 \(10^5\) \(3 \times 10^5\)

筆者備註:方便起見,原文中所有的 × 已經使用空格代替,23,24 兩組資料表示資料不是特殊構造,不具有 5 種性質。

  • 性質 1:不會出現環
  • 性質 2:層次數量很少
  • 性質 3:層次數量很大
  • 性質 4:無解
  • 性質 5:答案較小

筆者備註:在原文中,性質一是專門針對 FF 所設計的,也就是說 FF 只要有環就會 TLE,且沒環的資料仍然消耗了極高的時間。

評測環境說明:

  • 時間限制為 \(60s\)
  • 開啟快讀,\(O_2\) 優化。
  • dinic 和 ISAP 使用當前弧優化。
  • 使用 lemon 評測機測試

最後結果如下:

測試資料 EK dinic ISAP
1 \(0.171s\) \(0.625s\) \(0.265s\)
2 \(0.156s\) \(0.562s\) \(0.265s\)
3 \(0.625s\) \(0.828s\) \(0.390s\)
4 \(0.312s\) \(0.578s\) \(0.328s\)
5 \(0.046s\) \(\red{2.468s}\) \(0.218s\)
6 \(0.078s\) \(\red{5.546s}\) \(0.203s\)
7 \(0.109s\) \(\red{5.216s}\) \(0.328s\)
8 \(0.218s\) \(\red{7.812s}\) \(0.265s\)
9 \(0.375s\) \(\purple{1.281s}\) \(0.375s\)
10 \(0.156s\) \(0.781s\) \(0.187s\)
11 \(0.046s\) \(0.312s\) \(0.203s\)
12 \(\red{2.703s}\) \(0.875s\) \(0.328s\)
13 \(0.156s\) \(0.703s\) \(0.203s\)
14 \(0.328s\) \(0.500s\) \(0.218s\)
15 \(0.171s\) \(0.296s\) \(0.296s\)
16 \(0.234s\) \(0.562s\) \(0.296s\)
17 \(0.140s\) \(\red{4.687s}\) \(0.343s\)
18 \(0.031s\) \(\red{2.921s}\) \(0.296s\)
19 \(0.040s\) \(\red{2.359s}\) \(0.312s\)
20 \(0.078s\) \(\red{4.656s}\) \(0.390s\)
21 \(0.312s\) \(0.500s\) \(0.218s\)
22 \(0.203s\) \(\purple{1.000s}\) \(0.234s\)
23 \(0.062s\) \(0.343s\) \(0.265s\)
24 \(0.281s\) \(\purple{1.015s}\) \(0.328s\)
總用時 \(7.027s\) \(46.428s\) \(6.754s\)

根據上表,筆者總結如下:

  • dinic 在層次很深的時候會被卡掉。
  • EK 玄學吊打全場,但是被 #12 卡掉了。
  • ISAP 非常穩。

4. 總結

ISAP 的主要思路:一遍 BFS 分層,然後利用斷層巧妙尋找增廣路。

到目前為止,筆者已經講完了 EK,dinic,ISAP 求解最大流,接下來將會進入網路流的另外一個分支:費用流。

傳送門: