圖論——網路流

fujang發表於2020-12-26

網路流

最大流知識點梳理

  1. 流網路,不考慮反向邊,可以存在環,有向圖。
  2. 可行流,不考慮反向邊。
    (1)兩個條件:容量限制,流量守恆。
    (2)可行流的流量指從源點流出的流量-流入源點的流量。
    (3)最大流是指最大可行流。
  3. 殘留網路,考慮反向邊,和可行流一一對應,是原圖中邊數的二倍。其中正向的邊表示可以增加的流量,反向的邊表示可以退回去的流量。
    殘留網路的可行流f’+原圖的可行流f=原題的另一個可行流。
    (1)|f’+f| = |f|+|f’|
    (2)|f’|可能是負數
  4. 增廣路徑,在殘留網路中,從源點出發,沿著容量大於0的邊走,如果能走到終點,則為增廣路徑。

  5. (1)割的定義,把所有點分為兩個點集S、T,S包含源點,T包含匯點,兩個點集對於所有點來說補充不漏。
    (2)割的容量,不考慮反向邊,最小割指的是容量最小的個割。
    (3)割的流量,考慮反向邊f(S,T) <= c(S,T)。
    (4)對於任意可行流f,任意割[S,T],|f| = f(S,T)。
    (5)對於任意可行流f,任意割[S,T],|f| <= c(S,T)。
    (6)最大流最小割定理:①可行流f是最大流②可行流的殘留網路不存在增廣路③存在某個割[S,T],|f| = c(S,T)
  6. 演算法
    (1)EK O(nm^2)

AcWing 2171. EK求最大流

#include <bits/stdc++.h>
using namespace std;
int n, m, S, T;
const int N = 1010, M = 20010; // 殘留網路中雙倍的邊
int h[N], e[M], ne[M], f[M], idx;
bool vis[N];
int pre[N], dis[N];
// dis記錄增廣路徑的容量 pre記錄每個點前一條邊,也就是idx

void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx++;
	e[idx] = a, ne[idx] = h[b], f[idx] = 0, h[b] = idx++;
	// 正反邊轉換方式:異或,從0開始儲存
}

bool bfs() {
	memset(vis, false, sizeof vis);
	queue<int> q;
	q.push(S);
	vis[S] = 1, dis[S] = 1e8;
	while (q.size()) {
		int t = q.front();
		q.pop();
		for (int i = h[t]; i != -1; i = ne[i]) {
			int j = e[i];
			// 如果未遍歷過 且 流量大於0
			if (!vis[j] && f[i]) {
				dis[j] = min(dis[t], f[i]);
				vis[j] = 1, pre[j] = i;
				q.push(j);
				if (j == T) return true; 
			}
		}
	}
	return false;
}
 
void EK() {
	int ans = 0;
	while (bfs()) {
		ans += dis[T];
		// |f+f'|為更新後的可行流
		for (int i = T; i != S; i = e[pre[i] ^ 1])
			f[pre[i]] -= dis[T], f[pre[i] ^ 1]	+= dis[T]; // 更新殘留網路
	}
	cout << ans << endl;
}

int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m >> S >> T;
	for (int i = 1; i <= m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}
	EK();
	return 0;
}

相關文章