[算法] 網絡最大流 Dinic算法


前言

看到網上好多都用的鏈式前向星,就我在用 \(vector\) ……

定義

先來介紹一些相關的定義。(個人理解)

網絡

一個網絡是一張帶權的有向圖 \(G=(V,E)\) ,其中每任意一條邊 \((u,v)\) 的權值稱為這條邊的容量 \(c(u,v)\) 。若這條邊不存在,對應的容量就為 \(0\) 。其中包含兩個特殊的點:源點 \(S\) 與匯點 \(T\)

流量

\(f\) 為網絡的流函數,每一條邊都有對應的流量。對於合法的流函數包含以下性質。

  1. 容量限制: \(f(u,v)≤c(u,v)\)
  2. 斜對稱: \(f(u,v)=-f(v,u)\)
  3. 流量守恆:對於任意滿足不為源點或匯點的節點 \(k\) ,有: \(∑_{u∈E}f(u,k)=∑_{v∈E}f(k,v)\)

最大流

對於一個網絡,不難發現合法的流函數很多。這張圖的流量為 \(∑_{k∈E}f(S,k)\) ,顧名思義,最大流就是這張網絡的最大流量。

增廣路

存在一條從 \(S\)\(T\) 的路徑,使得路徑上所有的流量都不為 \(0\) ,則稱該路徑為增廣路。

殘量網絡

對於任意時刻,當前時刻網絡中,由所有結點與剩余容量大於 \(0\) 的邊構成的該網絡的子圖被稱為殘量網絡。

分層圖

在這個算法中,將殘量網絡分層后所構成的圖稱為分層圖。

Dinic算法

建圖

一條邊中需要包含以下信息:終點節點編號,邊的容量,相反的邊的編號。

struct Node {
	int to, value, rev;
	Node() {}
	Node(int T, int V, LL R) {
		to = T;//節點編號
		value = V;//邊的容量
		rev = R;//相反的邊的編號
	}
};

得雙向存邊,給出一條邊 \((A,B)\) ,其長度為 \(C\) ,建一條從 \(A\)\(B\) 的邊,權值為 \(C\) ,與之相反的邊權值為 \(0\)

for(int i = 1; i <= m; i++) {
	Quick_Read(A);
	Quick_Read(B);
	Quick_Read(C);
	int idA = v[A].size(), idB = v[B].size();
	v[A].push_back(Node(B, C, idB));
	v[B].push_back(Node(A, 0, idA));
}

雙向存邊是為了給后面 \(dfs\) 時,若存在更優解,可以使得程序反悔,重新走另一條路。這里暫時不懂可以繼續看后面的代碼再來理解這樣建圖的意義。

主體部分

  • 一遍 \(bfs\) ,將殘量網絡構造成分層圖,並求出當前殘量網絡是否存在增廣路。
  • 一遍 \(dfs\) ,在該分層圖中尋找增廣路,將這條讓這條增廣路消失。

重復上述兩個操作,直到當前網絡中不存在增廣路。

先來看 \(bfs\) 。其返回值為 \(bool\) ,意為該殘量網絡中是否還存在增廣路。層數 \(de[i]\) 的意義很明白: \(S\) 到達當前的點 \(i\) 的最小步數。而按照這樣的分層,每次只能將當前流量信息傳遞到下一層數節點上,可以很大程度上避免張冠李戴的情況。若 \(T\) 的層數為 \(1\) ,則說明當前 \(S\) 不能通向 \(T\) ,故而不存在增廣路,跳出循環。

bool bfs_Dinic() {//bfs將殘量網絡分層,返回是否圖中還存有增廣路 
	memset(de, 0, sizeof(de));//清空深度
	while(!q.empty())
		q.pop();
	q.push(s);
	de[s] = 1; be[s] = 0;
	while(!q.empty()) {
		int now = q.front();
		q.pop();
		int SIZ = v[now].size();
		for(int i = 0; i < SIZ; i++) {
			int next = v[now][i].to;
			if(v[now][i].value && !de[next]) {
				q.push(next);
				be[next] = 0;//分層圖改變,必須改變be[next]的值
				de[next] = de[now] + 1;
				if(next == t)
					return true;
			}
		}
	}
	return false;
}

再來看 \(dfs\) ,來判斷每一次的網絡是否可以傳遞,完成增廣的過程(以下代碼附上注釋)。這樣一次走了不止 \(1\) 條增廣路,節省了不少時間。

int dfs_Dinic(int now, int flow) {
	if(now == t || !flow)//找到匯點
		return flow;
	int i, surp = flow;//當前剩余流量 
	int SIZ = v[now].size();
	for(i = be[now]; i < SIZ && surp; i++) {
		be[now] = i;//i之前的增廣路已經更新完,不加否則會很慢!!
		int next = v[now][i].to, valedge = v[now][i].value;
		if(valedge && de[next] == de[now] + 1) {//&&前判斷是否可以走,即是剩余流量是否為0;&&后判斷是否滿足當前殘余網絡分層要求
			int maxnow = dfs_Dinic(next, Min(surp, valedge));
			if(!maxnow)//經驗定增廣完畢,de這個點不需要在遍歷了
				de[next] = 0;
			v[now][i].value -= maxnow;
			v[next][v[now][i].rev].value += maxnow;//反悔,可能找到其他路徑比當前這個流大 
			surp -= maxnow;
		}
	}
	return flow - surp;
}

最后在來說說剪枝, \(be\) 數組,在遍歷 \(i\) 時,\(be[i]\) 之前的路徑已經找完增廣路了,而對於當前這個分層圖,不存在會有更優解的情況,程序也不需要反悔,並不會影響程序的正確性,所以直接就不需要遍歷之前的點。

時間復雜度

單看這段程序,可以發現時間復雜度為 \(O(n^2m)\)

int Dinic() {
	int res = 0, flow = 0;
	while(bfs_Dinic())
		while(flow = dfs_Dinic(s, INF))//最大流的定義 
			res += flow;//流量守恆 
	return res;
}

而其實實際上並不需要這么多時間,參考資料得知可以處理\(10^4\)~\(10^5\)這樣規模的網絡。

C++代碼

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define Min(a, b) ((a) < (b) ? (a) : (b))
void Quick_Read(int &N) {
	N = 0;
	int op = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')
			op = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		N = (N << 1) + (N << 3) + (c ^ 48);
		c = getchar();
	}
	N *= op;
}
const int MAXN = 2e2 + 5;
struct Node {
	int to, value, rev;
	Node() {}
	Node(int T, int V, int R) {
		to = T;
		value = V;
		rev = R;
	}
};
vector<Node> v[MAXN];
queue<int> q;
int de[MAXN], be[MAXN];
int n, m, s, t;
bool bfs_Dinic() {
	bool flag = false;
	memset(de, 0, sizeof(de));
	while(!q.empty())
		q.pop();
	q.push(s);
	de[s] = 1; be[s] = 0;
	while(!q.empty()) {
		int now = q.front();
		q.pop();
		int SIZ = v[now].size();
		for(int i = 0; i < SIZ; i++) {
			int next = v[now][i].to;
			if(v[now][i].value && !de[next]) {
				q.push(next);
				be[next] = 0;
				de[next] = de[now] + 1;
				if(next == t)
					flag = true;
			}
		}
	}
	return flag;
}
int dfs_Dinic(int now, int flow) {
	if(now == t || !flow)
		return flow;
	int i, surp = flow;
	int SIZ = v[now].size();
	for(i = be[now]; i < SIZ && surp; i++) {
		be[now] = i;
		int next = v[now][i].to, valedge = v[now][i].value;
		if(valedge && de[next] == de[now] + 1) {
			int maxnow = dfs_Dinic(next, Min(surp, valedge));
			if(!maxnow)
				de[next] = 0;
			v[now][i].value -= maxnow;
			v[next][v[now][i].rev].value += maxnow;
			surp -= maxnow;
		}
	}
	return flow - surp;
}
long long Dinic() {
	long long res = 0;
	int flow = 0;
	while(bfs_Dinic())
		while(flow = dfs_Dinic(s, INF))
			res += flow * 1LL;
	return res;
}
void Read() {
	int A, B, C;
	Quick_Read(n);
	Quick_Read(m);
	Quick_Read(s);
	Quick_Read(t);
	for(int i = 1; i <= m; i++) {
		Quick_Read(A);
		Quick_Read(B);
		Quick_Read(C);
		int idA = v[A].size(), idB = v[B].size();
		v[A].push_back(Node(B, C, idB));
		v[B].push_back(Node(A, 0, idA));
	}
}
int main() {
	Read();
	printf("%lld", Dinic());
	return 0;
}

看看自己做對沒有吖

網絡最大流還可以應用於二分圖最大匹配。每條邊的左部點向右部點相連,從源點到每個左部點建一條邊,右部點與匯點建一條邊,每條邊的容量為1。

(附:匈牙利算法)

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define Min(a, b) ((a) < (b) ? (a) : (b))
#define Max(a, b) ((a) > (b) ? (a) : (b))
void Quick_Read(int &N) {
	N = 0;
	int op = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')
			op = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		N = (N << 1) + (N << 3) + (c ^ 48);
		c = getchar();
	}
	N *= op;
}
const int MAXN = 15e2 + 5;
struct Node {
	int to, value, rev;
	Node() {}
	Node(int T, int V, int R) {
		to = T;
		value = V;
		rev = R;
	}
};
vector<Node> v[MAXN];
queue<int> q;
int de[MAXN], be[MAXN];
int n, m, e, s, t;
bool bfs_Dinic() {
	bool flag = false;
	memset(de, 0, sizeof(de));
	while(!q.empty())
		q.pop();
	q.push(s);
	de[s] = 1; be[s] = 0;
	while(!q.empty()) {
		int now = q.front();
		q.pop();
		int SIZ = v[now].size();
		for(int i = 0; i < SIZ; i++) {
			int next = v[now][i].to;
			if(v[now][i].value && !de[next]) {
				q.push(next);
				be[next] = 0;
				de[next] = de[now] + 1;
				if(next == t)
					flag = true;
			}
		}
	}
	return flag;
}
int dfs_Dinic(int now, int flow) {
	if(now == t || !flow)
		return flow;
	int i, surp = flow;
	int SIZ = v[now].size();
	for(i = be[now]; i < SIZ && surp; i++) {
		be[now] = i;
		int next = v[now][i].to, valedge = v[now][i].value;
		if(valedge && de[next] == de[now] + 1) {
			int maxnow = dfs_Dinic(next, Min(surp, valedge));
			if(!maxnow)
				de[next] = 0;
			v[now][i].value -= maxnow;
			v[next][v[now][i].rev].value += maxnow;
			surp -= maxnow;
		}
	}
	return flow - surp;
}
int Dinic() {
	int res = 0;
	int flow = 0;
	while(bfs_Dinic())
		while(flow = dfs_Dinic(s, INF))
			res += flow;
	return res;
}
void Read() {
	int A, B;
	Quick_Read(n);
	Quick_Read(m);
	Quick_Read(e);
	t = n + m + 1;
	for(int i = 1; i <= e; i++) {
		Quick_Read(A);
		Quick_Read(B);
		B += Max(n, m);
		int idA = v[A].size();
		int idB = v[B].size();
		v[A].push_back(Node(B, 1, idB));
		v[B].push_back(Node(A, 0, idA));
	}
	for(int i = 1; i <= n; i++) {
		int idi = v[i].size();
		int ids = v[s].size();
		v[s].push_back(Node(i, 1, idi));
		v[i].push_back(Node(s, 0, ids));
	}
	for(int i = 1; i <= m; i++) {
		int idi = v[i + Max(n, m)].size();
		int idt = v[t].size();
		v[i + Max(n, m)].push_back(Node(t, 1, idt));
		v[t].push_back(Node(i + Max(n, m), 0, idi));
	}
}
int main() {
	Read();
	printf("%d", Dinic());
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM