圖論專題-學習筆記:ISAP 求解最大流
1. 前言
本篇博文將會重點講解 ISAP 求解最大流。
ISAP 求解最大流,是目前筆者知道的 除了 HLPP 之外的速度最快的最大流演算法。
在學習 ISAP 求解最大流之前,您需要對以下知識有所瞭解,包括但不限於:網路流基礎定義,FF/EK 求解最大流的 思路,dinic 求解最大流的 程式碼實現。
如果您對上述部分內容不熟悉,可以前往筆者所寫的以下博文檢視:
- 網路流基礎定義:演算法學習筆記:網路流#1——有關內容+演算法導航
- FF/EK 求解最大流的 思路:演算法學習筆記:網路流#2——EK 求解最大流
- dinic 求解最大流的 程式碼實現:演算法學習筆記:網路流#3——dinic 求解最大流
2. 模板
模板題:P3376 【模板】網路最大流
2.1 詳解
dinic 演算法已經足夠高效了,但是非常遺憾,dinic 仍然可以被卡掉,而且出現這種資料就代表 dinic 一定會 TLE,可以見最後面的資料比較。
不行,不穩定的演算法我們不要,我們需要一種更加穩定的演算法來處理最大流問題。
於是 ISAP 出現了。
ISAP 的核心思路就是:先用 一遍 BFS 從 \(t\) 開始 分層,同時記錄 \(gap_i\)
尋找增廣路的過程中,一旦有一個點接受到的流量不能全部流出,那麼就將這個點的層數提高 1,如果在提高完之後出現了斷層(有一層沒有點了),意味著演算法結束。
大體分為 3 個步驟:
- 分層
- 找增廣路以及提高層數
- 出現斷層則結束演算法
演算法好理解,但是正確性呢?
2.2 正確性證明
ISAP 的正確性證明可能需要感性理解一下。
根據上面的演算法步驟,當一個點推流推完的時候,這個點的層數不提高。
為什麼?因為此時這個點的層數已經足夠其推流了,不需要再提高,而且根據 dinic 演算法的要求,流量只能在相鄰層之間移動。
那麼假設當前點推流推不出去了,這個時候需要提高層數,但是為什麼這樣就一定正確了呢?
想一個問題:如果當前的點無法推流了,那麼前面的點呢?
是不是也無法推流了呀!那麼因此如果我們提高了這個點的層數,前面的點也必須要提高層數(否則無法推流),此時就能夠保證至少這個點接受流量不變。
而如果後面的點有層次更高的點,就可以繼續推流,存在增廣路;如果沒有了,都是層次比較低的點,此時滿足以下幾個條件:
- 層次比較低的點推流完畢,無法再推流(即使得到 \(INF\) 的流量)。
- 由一,不存在增廣路,滿足演算法結束條件。
- 於此同時,由於後面的點層次比較低,又推流完畢,沒法提高層次,這個時候就會 出現斷層,滿足演算法結束條件。
綜上,演算法正確性成立。
而 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 求解最大流,接下來將會進入網路流的另外一個分支:費用流。
傳送門: