網路流學習筆記

来源:https://www.cnblogs.com/reasa/archive/2023/07/14/17546198.html
-Advertisement-
Play Games

網路流 何為網路流 想要弄清楚網路流,首先要知道網路的概念,通常在運籌學中,網路是指一個有向圖$G\ =\ (V,E)$ 。其每條邊$(u,v)\in E$都有一個權值$c(u,v)$,稱為這條邊的流量(Capacity),還有兩個特殊的點,一個是源點(Source),一個是匯點(Sink)在圖論中 ...


網路流

何為網路流

       想要弄清楚網路流,首先要知道網路的概念,通常在運籌學中,網路是指一個有向圖$G\ =\ (V,E)$ 。其每條邊$(u,v)\in E$都有一個權值$c(u,v)$,稱為這條邊的流量(Capacity),還有兩個特殊的點,一個是源點(Source),一個是匯點(Sink)在圖論中,網路流(英語:Network flow)在作為網路的有向圖中分配流,使一條邊的流量不會超過它的容量。

網路流的性質

1.容量限制

$\ \ \ \ $ 對於有向圖\(G\)中的每一條邊上所流經的流量不得超過其容量,即 \(f(u,v) \ ≤ \ c(u,v)\)

2.斜對稱性

$\ \ \ \ $ 每條邊的流量與相反邊的流量之和為0,即 \(f(u,v) \ = \ -f(u,v)\)

3.流守恆性

$\ \ \ \ $ 從源點流出的流量等於匯點流入的流量,即 \(\forall x\in V-\{s,t\},\sum_{(u,x)\in E}f(u,x)=\sum_{(x,v)\in E}f(x,v)\)

$\ \ \ \ $ 現在想象下麵一個情景,工廠里有一個運輸液體的傳輸管道,工廠在每條管道的都設置了防止倒流的裝置,因為壓強問題,所以每條管道在單位時間內的容量有限,這就是一個網路流模型了。

事實上,網路流的應用已遍及通訊、運輸、電力、工程規劃、任務分派、設備更新以及電腦輔助設計等眾多領域。

網路流的常見問題

$\ \ \ \ $ 網路流問題中常見的有以下三種:最大流,最小割,費用流。

最大流

$\ \ \ \ $ 顧名思義,就是求從源點到匯點的最大流量。下麵介紹幾種常見的方法,其中Dinic演算法幾乎能完成所有與最大流相關的問題,因為他的複雜度比較優秀。

Ford-Fulkerson增廣

$\ \ \ \ $ Ford-Fulkerson增廣是計算最大流演算法的一類統稱,核心思想是貪心,通過尋找增廣路來更新並求解最大流。其中,尋找增廣路其實就是尋找從源點到匯點的剩餘容量非空的路徑,對於一條增廣路,我們給每一條\((u,v)\)都加上等量的流量,以令整個網路的流量增加,這一過程被稱為增廣(Augment)。,但是,在執行過程中,會發現這樣的思路有可能導致增廣了一些原來不應存在於最大流的路徑,怎麼辦?

$\ \ \ \ $ 我們可以利用網路流的斜對稱性,在增廣時進行退流操作,如果增廣出了\((u,v)\)之間的雙向流量實際上可以將經過\((v,u)\)的流量交換來抵消成單向流量。
退流操作帶來的「抵消」效果使得我們無需擔心我們按照「錯誤」的順序選擇了增廣路。

接下類就是對Ford-Fulkerson增廣演算法的實現了,對於 Ford–Fulkerson 增廣的不同實現,時間複雜度也各不相同。其中較主流的實現有 Edmonds–Karp, Dinic, SAP, ISAP 等演算法,我會將自己理解了一些的演算法介紹出來,至於其他的,下次一定(doge。

EK演算法(Edmonds–Karp)

演算法思想

EK演算法就是通過BFS不斷尋找增廣路並不斷更新最大流,直至在網路中再也找不到增廣路為止。上面講過,僅僅進行增廣操作的話,最後得到的答案是錯誤的,因此需要退流,而為了追求速度,我們希望能在最短時間找到反向邊,我們發現,利用鄰接矩陣可以快速的找到反向邊,因為鄰階矩陣就具有對稱性,但是因為鄰接矩陣相當於一個二維數組,無論是空間上還是時間上都不是很好,因此在網路流中的通常使用鏈式前向星來存儲。其中,一個常用的技巧是,我們令邊從偶數(通常為 0)開始編號,併在加邊時總是緊接著加入其反向邊使得它們的編號相鄰。由此,我們可以令編號為 \(i\) 的邊和編號為 $i \oplus 1 $的邊始終保持互為反向邊的關係。

存邊代碼如下:

inline void addedge(int u,int v,int c){
	edge[++tot].c = c;
	edge[tot].to = v;
	edge[tot].from = head[u];
	head[u] = tot;
	edge[++tot].c = 0;
	edge[tot].to = u;
	edge[tot].from = head[v];
	head[v] = tot;
}

更新代碼如下:

inline void update(){
	int x = t;
	while (x != s){
		int v = pre[x];
		edge[v].c -= dis[t]; //c是剩餘容量
		edge[v^1].c += dis[t];
		x = edge[v^1].to;
	}
	ans += dis[t];
}

適用範圍

時間複雜度為\(O(nm^2)\),一般能處理\(10^3 ~ 10^4\)規模的網路。

完整代碼 \(Code\)

#include <bits/stdc++.h>
#define MAXN 505050
#define int long long
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char c = getchar();
	while (!isdigit(c)){
		if (c == '-'){
			f = -1;
		}
		c = getchar();
	}
	while (isdigit(c)){
		x = x*10+c-'0';
		c = getchar();
	}
	return x*f;
}
int n,m,s,t;
int tot = 1,vis[MAXN],pre[MAXN],head[MAXN],flag[2500][2500];
int dis[MAXN];
int ans;
struct node {
	int from,to,c,flow;
}edge[MAXN];
inline void addedge(int u,int v,int c){
	edge[++tot].to = v;
	edge[tot].from = head[u];
	edge[tot].c = c;
	head[u] = tot;
	edge[++tot].to = u;
	edge[tot].from = head[v];
	edge[tot].c = 0;
	head[v] = tot;
}
inline bool bfs(){
	for (int i=1;i<=n;i++){
		vis[i] = 0;
	}
	queue<int> q;
	q.push(s);
	vis[s] = 1;
	dis[s] = 2005020600;
	while (!q.empty()){
		int x = q.front();
		q.pop();
		for (int i=head[x];i;i=edge[i].from){
			int v = edge[i].to;
			if (!edge[i].c) continue;
			if (vis[v]) continue;
			dis[v] = min(dis[x],edge[i].c);
			pre[v] = i;
			q.push(v);
			vis[v] = 1;
			if (v == t)return 1;
		}
	}
	return 0;
}
inline void update(){
	int x = t;
	while (x!=s){
		int v = pre[x];
		edge[v].c-=dis[t];
		edge[v^1].c+=dis[t];
		x = edge[v^1].to;
	}
	ans+=dis[t];
}
signed main(){
	n = read(),m = read(),s = read(),t = read();
	for (int i=1;i<=m;i++){
		int u = read(),v = read(),w = read();
		if (flag[u][v] == 0){
			addedge(u,v,w);
			flag[u][v] = tot;
		}
		else {
			edge[flag[u][v]-1].c+=w;
		}
	}
	while (bfs()!=0){
		update();
	}
	cout<<ans<<'\n';
	return 0;
}

Dinic演算法

Dinic演算法在大部分圖上的效率都十分優秀,因此也算是處理網路流問題中較為常見的,其時間複雜度是\(O(|V|^2|E|)\) 因為本人太菜不會證明,故不列出證明(

演算法思想

EK演算法可能會遍歷整個殘量網路(所有邊均為其殘量的 \(G_f(V,E_f)\)),而只為尋找一條增廣路,顯然只開了單線程,那麼我們能不能找到一種方法,像開多線程一樣,一次尋找多條增廣路呢?

這時候就要我們的Dinic演算法出馬了。

分層圖&\(DFS\)

考慮在進行增廣前先對網路進行\(BFS\)分層,即根據節點到源點的的距離(dis)把節點分成若幹層。對於每一個節點\(u\),我們用\(d(u)\)來表示他的層次,即\(s\)\(x\)的最少邊數,在殘量網路中,滿足\(d(u) = d(u)+1\)的邊\((u,v)\)構成的子圖被稱為分層圖,而分層圖顯然是一張有向無環圖(Directed acyclic graph),為什麼要構建分層圖呢?在解釋原因前,要先瞭解Dinic演算法還要通過\(DFS\)進行增廣,在\(DFS\)中,從\(S\)開始,每次我們從下一層的隨機一個點開始跑,就這樣一直跑到匯點,然後再一層層回溯回去,繼續找這一層的另外的點再往下搜索,顯然的,這樣操作可以保證同時多增廣的需求,然後對於每一層的最大流我們再進行合併,就能求出該網路圖的最大流了。

演算法框架

1.在殘量網路上BFS求出節點的層次,構造分層圖

2.在分層圖上DFS尋找增廣路,在回溯時同時更新邊權

適用範圍

時間複雜度為\(O(n^2m)\),一般能處理\(10^4 ~ 10^5\)的網路。

同時,Dinic演算法還能用來求二分圖,複雜度比匈牙利演算法低,但還沒學,所以在這就不介紹了。

代碼 \(Code\)

#include <bits/stdc++.h>
using namespace std;
const long long inf=2005020600;
int n,m,s,t,u,v;
long long w,ans,dis[520010];
int tot=1,now[520010],head[520010]; 

struct node {
	int to,net;
	long long val;
} e[520010];

inline void add(int u,int v,long long w) {
	e[++tot].to=v;
	e[tot].val=w;
	e[tot].net=head[u];
	head[u]=tot;
	
	e[++tot].to=u;
	e[tot].val=0;
	e[tot].net=head[v];
	head[v]=tot;
}

inline int bfs() {  //在慘量網路中構造分層圖 
	for(register int i=1;i<=n;i++) dis[i]=inf;
	queue<int> q;
	q.push(s);
	dis[s]=0;
	now[s]=head[s];
	while(!q.empty()) {
		int x=q.front();
		q.pop();
		for(register int i=head[x];i;i=e[i].net) {
			int v=e[i].to;
			if(e[i].val>0&&dis[v]==inf) {
				q.push(v);
				now[v]=head[v];
				dis[v]=dis[x]+1;
				if(v==t) return 1;
			}
		}
	}
	return 0;
}

inline int dfs(int x,long long sum) {  //sum是整條增廣路對最大流的貢獻
	if(x==t) return sum;
	long long k,res=0;  //k是當前最小的剩餘容量 
	for(register int i=now[x];i&&sum;i=e[i].net) {
		now[x]=i;  //當前弧優化 
		int v=e[i].to;
		if(e[i].val>0&&(dis[v]==dis[x]+1)) {
			k=dfs(v,min(sum,e[i].val));
			if(k==0) dis[v]=inf;  //剪枝,去掉增廣完畢的點 
			e[i].val-=k;
			e[i^1].val+=k;
			res+=k;  //res表示經過該點的所有流量和(相當於流出的總量) 
			sum-=k;  //sum表示經過該點的剩餘流量 
		}
	}
	return res;
}

int main() {
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d%lld",&u,&v,&w);
		add(u,v,w);
	}
	while(bfs()) {
		ans+=dfs(s,inf);  //流量守恆(流入=流出) 
	}
	printf("%lld",ans);
	return 0;
}

最小割和費用流還沒學完,學完了再寫(


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Docker 可以運行隔離的容器,包括應用程式和其依賴項,與主機操作系統分離。預設情況下,容器是臨時的,這意味著容器中存儲的任何數據在終止後都將丟失。為瞭解決這個問題併在容器生命周期內保留數據,Docker 提供了各種數據持久化方法。 - **Docker 捲** - **綁定掛載** - **Do ...
  • # Scala基礎篇(持續更新中...) ## 數據類型 下表中列出的數據類型都是對象,可以直接對它們調用方法。 | 數據類型 | 描述 | | | | | Byte | 8位有符號補碼整數。數值區間為 -128 到 127 | | Short | 16位有符號補碼整數。數值區間為 -32768 到 ...
  • ## Socket編程(網路通信) #### 伺服器端Demo(ServreSocket) ​ 創建服務端時,如果不提供IP地址,則預設為本地連接(127.0.0.1),但是一定需要手動配置監聽埠! ```java public static void main(String[] args) { ...
  • #include <iostream> #include <Windows.h> #include <WinSock2.h> std::string GetLastErrorMessage() { DWORD errorCode = WSAGetLastError(); LPSTR errorMes ...
  • 在互聯網世界中,驗證碼作為一種防止機器人訪問的工具,是爬蟲最常遇到的阻礙。驗證碼的類型眾多,從簡單的數字、字母驗證碼,到複雜的圖像識別驗證碼,再到更為高級的互動式驗證碼,每一種都有其獨特的識別方法和應對策略。在這篇文章中,我們將一一介紹各種驗證碼的工作原理和使用[2Captcha](https://... ...
  • ## 教程簡介 巨集語言Visual Basic for Application(VBA).Visual Basic是windows環境下開發應用軟體的一種通用程式設計語言,功能強大,簡便易用。 Excel巨集是Excel中的一種編程功能,它可以讓用戶錄製一系列的操作,以便在需要時自動執行這些操作。用戶 ...
  • ## 教程簡介 Apache JMeter 是 Apache 組織基於 Java 開發的壓力測試工具,用於對軟體做壓力測試。JMeter 最初被設計用於 Web 應用測試,但後來擴展到了其他測試領域,可用於測試靜態和動態資源,如靜態文件、Java 小服務程式、CGI 腳本、Java 對象、資料庫和 ...
  • ## Miller_rabin 素數測試 一種用來判斷素數的演算法。 ### 前置芝士 #### 威爾遜定理 若 $p$ 為素數,$(p-1)! \equiv -1 (\mod p)$。 證明: 充分性證明: 如果 $p$ 不是素數,那麼他的因數必定存在於$ 1,2,3,\dots,p−1$ 之中,所 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...