1. 程式人生 > >網路流彙總

網路流彙總

網路流

網路流(network-flows)是一種類比水流的解決問題方法,與線性規劃密切相關。網路流的理論和應用在不斷髮展,出現了具有增益的流、多終端流、多商品流以及網路流的分解與合成等新課題。網路流的應用已遍及通訊、運輸、電力、工程規劃、任務分派、裝置更新以及計算機輔助設計等眾多領域。

定義 有向圖G = (V, E)中:

  • 有唯一的一個源點S(入度為0:出發點)
  • 有唯一的一個匯點T(出度為0:結束點)
  • 圖中每條弧(u, v)都有一非負容量c(u, v)

滿足上述條件的圖G稱為網路流圖。記為:G = (u, v, c) 可行流 每條弧(u, v)上給定一個實數f(u, v),滿足:有0<=f(u, v)<=c(u, v),則f(u, v)稱為弧(u, v)上的流量。 如果有一組流量滿足條件:

  • 源點s:流出量=整個網路的流量
  • 匯點t:流入量=整個網路的流量
  • 中間點:總流入量=總流出量

那麼整個網路中的流量成為一個可行流。

網路流的三個基本性質

  • 容量限制:對任意u, v ∈ V,f(u, v)<=c(u, v)。
  • 反對稱性:對任意u, v ∈ V,f(u, v) = -f(v, u)。從u到v的流量一定是從v到u的流量的相反值。
  • 流守恆性:對任意u,若u不為S或T,一定有∑f(u,v)=0,(u,v)∈E。即u到相鄰節點的流量之和為0,因為流入u的流量和u點流出的流量相等,u點本身不會"製造"和"消耗"流量。

容量網路&流量網路&殘留網路

  • 容量網路就是關於容量的網路 基本是不改變的(極少數問題需要變動)
  • 流量網路就是關於流量的網路 在求解問題的過程中 通常在不斷的改變 但是總是滿足上述三個性質 調整到最後就是最大流網路 同時也可以得到最大流值
  • 殘留網路往往概括了容量網路和流量網路 是最為常用的 殘留網路=容量網路-流量網路 這個等式是始終成立的 殘留值當流量值為負時甚至會大於容量值 流量值為什麼會為負?有正必有負,記住斜對稱性!

割&割集

  • 割:E是弧的集合,設E·為E的一個子集,如果G在刪除 E·之後不再連通,則E~為G的割。
  • 割集:容量網路G = (V, E, C),Vs,Vt為源、匯點,若有邊集E·為E的子集,將G為兩個子圖G1,G2,即點集V被部分其為兩個頂點集合分別S,~S,必有S∪ ~S = V,S∩ ~S = Ø,Vs ∈ S, Vt ∈ ~S。若有邊集E·為E的子集,且滿足下列兩個性質,則稱E·為G的割集),記為E· = (S, ~S)。 割集(S, ~S)中所有始點在S,終點在 ~S的邊的容量之和,成為(S, ~S)的割集容量,記為C(S, ~S)。容量網路G的割集有很多個,其中割集容量最小這成為網路G的最小割集容量(簡稱最小割集)。 性質:
    1. 若把整個截集的弧從網路G=(V,E,C)中丟去,則不存在從vs和vt的有向路,即圖(V,E-E·)不連通。
    2. 只要沒把整個截集刪去,就存在從vs和vt的有向路,即當E’‘為E的真子集,圖G(V,E-E··)仍連通。 由此可知,截集是從起點vs到終點vt的必經之路。

網路流與最大流

定義

給定指定的一個有向圖,其中有兩個特殊的點源S(Sources)和匯點T(Sinks),每條邊有指定的容量(Capacity),求滿足條件的從S到T的最大流(MaxFlow)。 The network flow problem considers a graph G with a set of sources S and sinks T and for which each edge has an assigned capacity (weight), and then asks to find the maximum flow that can be routed from S to T while respecting the given edge capacities.

通俗的講,就是由若干個運貨點,一個是起點,一個是終點,有一些運貨點由路相連,每條路有容量限制,走過那條路時運送的貨物不能超過其中的容量限制,求最大流就是求從起點運送儘量多的貨物到終點,到達終點的貨物個數。 例如: 在這裡插入圖片描述 該圖的最大流為4。

ans = f(v2, T) + f(v4, T)
	= min(f(v1, v2), c(v2, T)) + min(f(v3, v4), c(v4, T))
	= min(min(f(S, v1), c(v1, v2)), 3) + min(min(f(S, v3), c(v3, v4)), 1)
	= min(min(min(INF, c(S, v1)), 7), 3) + min(min(min(INF, c(S, v3)), 9), 1)
	= 3+1
	= 4

那麼如何求最大流呢?可以採用著名的Dinic演算法。

最大流演算法(Dinic)
Dinic演算法的基本思路:
  • 根據殘量網路計算層次圖。
  • 在層次圖中使用DFS進行增廣直到不存在增廣路
  • 重複以上步驟直到無法增廣
時間複雜度

因為在Dinic的執行過程中,每次重新分層,匯點所在的層次是嚴格遞增的,而n個點的層次圖最多有n層,所以最多重新分層n次。在同一個層次圖中,因為每條增廣路都有一個瓶頸,而兩次增廣的瓶頸不可能相同,所以增廣路最多m條。搜尋每一條增廣路時,前進和回溯都最多n次,所以這兩者造成的時間複雜度是O(nm);而沿著同一條邊(i,j)不可能列舉兩次,因為第一次列舉時要麼這條邊的容量已經用盡,要麼點j到匯不存在通路從而可將其從這一層次圖中刪除。綜上所述,Dinic演算法時間複雜度的理論上界是O(n^2*m)。

小貼士:

一般情況下在Dinic演算法中,我們只記錄某一邊的剩餘流量.

  • 殘量網路:包含反向弧的有向圖,Dinic要迴圈的,每次修改過的圖都是殘量網路,
  • 層次圖:分層圖,以[從原點到某點的最短距離]分層的圖,距離相等的為一層,(比如上圖的分層為{s},{1,3},{2, 4},{t})
  • 增廣 :在現有流量基礎上發現新的路徑,擴大發現的最大流量(注意:增加量不一定是這條路徑的流量,而是新的流量與上次流量之差)
  • 增廣路:在現有流量基礎上發現的新路徑.
  • 剩餘流量:當一條邊被增廣之後(即它是增廣路的一部分,或者說增廣路通過這條邊),這條邊還能通過的流量.
  • 反向弧:我們在Dinic演算法中,對於一條有向邊,我們需要建立另一條反向邊(弧),當正向(輸入資料)邊剩餘流量減少I時,反向弧剩餘流量增加I
    • 為什麼需要反向弧? 我們知道,當我們在尋找增廣路的時候,在前面找出的不一定是最優解,如果我們在減去殘量網路中正向邊的同時將相對應的反向邊加上對應的值,我們就相當於可以反悔從這條邊流過。

(HDU1532)

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 1e4;
const int INF = 1e9+7;
int n, m;
struct edge{
	int v, w, next;
}Edge[maxn];
int head[maxn], cnt = 0, dis[maxn];
void init(){
	cnt = 0;
	memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w){
	Edge[cnt].v = v;
	Edge[cnt].w = w;
	Edge[cnt].next = head[u];
	head[u] = cnt++;
}
void add(int u, int v, int w){
	addEdge(u, v, w);
	addEdge(v, u, 0);
}
bool BFS(){
	memset(dis, -1, sizeof(dis));
	queue<int> q;
	q.push(1);
	dis[1] = 0;
	while(!q.empty()){
		int u = q.front();
		q.pop();
		for(int i = head[u]; i != -1; i = Edge[i].next){
			int v = Edge[i].v;
			if(Edge[i].w && dis[v]==-1){
				q.push(v);
				dis[v] = dis[u]+1;
			}
		}
	}
	return dis[m]!=-1;
}
int DFS(int u, int flow){
	int maxFlow = 0;
	if(u == m) return flow;
	for(int i = head[u]; i != -1; i = Edge[i].next){
		int v = Edge[i].v, w;
		if(dis[v] == dis[u]+1 && Edge[i].w && flow && (w = DFS(v, min(flow, Edge[i].w)))){
			flow-=w;
			maxFlow+=w;
			Edge[i].w-=w;
			Edge[i^1].w+=w;
		}
	}
	return maxFlow;
}
void dinic(){
	int maxFlow = 0;
	while(BFS()){
		maxFlow += DFS(1, INF);
	}
	printf("%d\n", maxFlow);
}
int main(){
	while(scanf("%d%d", &n, &m) != EOF){
		init();
		for(int i = 1; i <= n; i++){
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			add(u, v, w);
		}
		dinic();
	}
	return 0;
}