網絡流彙總

網絡流

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

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

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

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

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

那麼整個網絡中的流量成爲一個可行流。

網絡流的三個基本性質

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

容量網絡&流量網絡&殘留網絡

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

割&割集

  • 割:E是弧的集合,設E·爲E的一個子集,如果G在刪除 E·之後不再連通,則E~爲G的割。
  • 割集:容量網絡G=(V,E,C)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)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)

問題描述
每當農場約翰的田地下雨時,一個池塘形成了貝西最喜歡的三葉草補丁。這意味着三葉草被水覆蓋了一段時間並且需要相當長的時間才能再生。因此,Farmer John建造了一套排水溝,以便Bessie的三葉草補丁永遠不會被水覆蓋。相反,水被排放到附近的溪流中。作爲一名王牌工程師,Farmer John還在每個溝渠開始時安裝了調節器,因此他可以控制水流入該溝渠的速度。
農夫約翰不僅知道每個溝渠每分鐘可以運輸多少加侖的水,而且還知道溝渠的確切佈局,這些溝渠從池塘中流出並相互進入並在潛在的複雜網絡中流動。
根據所有這些信息,確定水可以從池塘運輸到水流中的最大速率。對於任何給定的溝渠,水只沿一個方向流動,但可能有一種方式,水可以流動。
輸入
輸入包括幾種情況。對於每種情況,第一行包含兩個空格分隔的整數,N(0 <= N <= 200)和M(2 <= M <= 200)。N是Farmer John挖的溝渠數量。M是那些溝渠的交叉點數。交叉口1是池塘。交點M是流。以下N行中的每一行包含三個整數,Si,Ei和Ci。Si和Ei(1 <= Si,Ei <= M)表示該溝流動的交叉點。水將從Si流到Ei。Ci(0 <= Ci <= 10,000,000)是水流過溝渠的最大速率。
產量
對於每種情況,輸出一個整數,即水可以從池塘中排空的最大速率。
樣本輸入
5 4
1 2 40
1 4 20
2 4 20
2 3 30
3 4 10
樣本輸出
50

#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;
} 

網絡流-最小費用最大流

最小費用最大流問題是經濟學和管理學中的一類典型問題。在一個網絡中每段路徑都有“容量”和“費用”兩個限制的條件下,此類問題的研究試圖尋找出:流量從A到B,如何選擇路徑、分配經過路徑的流量,可以在流量最大的前提下,達到所用的費用最小的要求。如n輛卡車要運送物品,從A地到B地。由於每條路段都有不同的路費要繳納,每條路能容納的車的數量有限制,最小費用最大流問題指如何分配卡車的出發路徑可以達到費用最低,物品又能全部送到。

定義

給定網絡G=(V,E,C)G = (V, E, C) 每一弧(vi,vj)(v_i,v_j)上,除了已給容量cijc_{ij}外,還給了一個單位流量的費用w(vi,vj)0w(v_i,v_j)\geq0(簡記爲bijb_{ij})。所謂最小費用最大流問題就是要求一個最大流ff,使流的總輸送費用最小,即求ff^*,使w(f)=min(vi,vj)Abijfijw(f^*) =min\sum_{(v_i,v_j)\in A}b_{ij}f_{ij}

費用流算法(SPFA版本)
算法的基本思路:
  • 通過SPFA找一條vsvtv_s\rightarrow v_t的最短路。“距離”使用該路徑上的邊的單位費用之和來衡量。
  • 找出這條路徑上的邊的容量的最小值flow,則當前最大流maxFlow擴充flow,同時當前最小費用minCost擴充 f*minDis(s,t)。
  • 將這條路徑上的每條正向邊的容量都減少flow,每條反向邊的容量都增加flow。
  • 重複以上步驟直到無法找到從源點到達匯點的路徑。

(P3381洛谷)

題目描述
如題,給出一個網絡圖,以及其源點和匯點,每條邊已知其最大流量和單位流量費用,求出其網絡最大流和在最大流情況下的最小費用。
輸入格式:
第一行包含四個正整數N、M、S、T,分別表示點的個數、有向邊的個數、源點序號、匯點序號。
接下來M行每行包含四個正整數ui、vi、wi、fi,表示第i條有向邊從ui出發,到達vi,邊權爲wi(即該邊最大流量爲wi),單位流量的費用爲fi。
輸出格式:
一行,包含兩個整數,依次爲最大流量和在最大流量情況下的最小費用。
input
4 5 4 3
4 2 30 2
4 3 20 3
2 3 20 1
2 1 30 9
1 3 40 5
output
50 280

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read(){
    int t=1,num=0;
    char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')t=-1;c=getchar();}
    while(c>='0'&&c<='9'){num=num*10+c-'0';c=getchar();}
    return num*t;
}
const int maxn = 200010;
const int eMaxn = 5015;
const int INF = 0x3f3f3f3f;
int N, M, S, T;
struct Edge{
    int v, w, f, next;
}edge[maxn];
int head[eMaxn], cnt;
int dis[eMaxn], path[eMaxn];
bool vis[eMaxn];
void init(){
	N+=10;
    cnt = 0;
    memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w, int f){
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].f = f;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}
void add(int u, int v, int w, int f){
    addEdge(u, v, w, f);
    addEdge(v, u, 0, -f);
}
bool SPFA(){
    fill(dis, dis+N, INF);
    memset(vis, false, sizeof(vis));
    memset(path, -1, sizeof(path));
    queue<int> q;
    q.push(S);
    vis[S] = true;
    dis[S] = 0;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = head[u]; i!=-1; i = edge[i].next){
            int v = edge[i].v;
            if(edge[i].w && dis[v] > dis[u]+edge[i].f){
                path[v] = i; 
                dis[v] = dis[u]+edge[i].f;
                if(!vis[v]){
                    q.push(v), vis[v] = true;
                }
            }
        }
    }
    return path[T]!=-1;
}
void MCMF(){
    int maxFlow = 0, minCost = 0;
    while(SPFA()){
        int MIN = INF;
        for(int u = path[T]; u!=-1; u = path[edge[u^1].v]){
            if(MIN > edge[u].w) MIN = edge[u].w;
        }
        for(int u = path[T]; u!=-1; u = path[edge[u^1].v]){
            minCost+=MIN*edge[u].f;
            edge[u].w-=MIN;
            edge[u^1].w+=MIN;
        }
        maxFlow += MIN;
    }
    printf("%d %d\n", maxFlow, minCost);
}
int main(){
    
    N = read(), M = read(), S = read(), T = read();
    init();
	for(int i = 0; i < M; i++){
        int u, v, w, f;
        u = read(), v = read(), w = read(), f = read();
        add(u, v, w, f);
    }
    MCMF();
    return 0;
} 

網絡流-最大流最小割

最大流最小割定理是網絡流理論的重要定理。是指在一個網絡流中,能夠從源點到達匯點的最大流量等於如果從網絡中移除就能夠導致網絡流中斷的邊的集合的最小容量和。即在任何網絡中,最大流的值等於最小割的容量。

相關定理

定理一:
  如果ff是網絡中的一個流,CUT(S,T)CUT(S,T)是任意一個割,那麼ff的值等於正向割邊的流量與負向割邊的流量之差。
證明:
  設XXYY是網絡中的兩個頂點集合,用f(X,Y)f(X,Y)表示從XX中的一個頂點指向YY的一個頂點的所有弧(弧尾在X中,弧頭在Y中:XYX \rightarrow Y )的流量和。只需證明:f=f(S,T)f(T,S)f=f(S,T)-f(T,S)即可。
下列結論成立:
如果X∩Y= ,那麼:f(X,(Y1Y2))=f(X,Y1)+f(X,Y2)f(X,(Y1∪Y2))=f(X,Y1)+f(X,Y2)f((X1X2),Y)=f(X1,Y)+f(X2,Y)f((X1∪X2),Y)=f(X1,Y)+f(X2,Y)成立。
  根據網絡流的特點:
如果V既不是源點也不是匯點,那麼:f(V,ST)f(ST,V)=0f({V},S∪T)-f(S∪T,{V})=0;任何一個點,流入的與流出的量相等。
如果V是源,那麼:f(V,ST)f(ST,V)=ff({V},S∪T)-f(S∪T,{V})=f
對於S中的所有點V都有上述關係式,相加得到:f(S,ST)f(ST,S)=ff(S,S∪T)-f(S∪T,S)=f
又因爲: f(S,ST)f(ST,S)=(f(S,S)+f(S,T))(f(S,S)+f(T,S))=f(S,T)f(T,S)f(S,S∪T)-f (S∪T,S)= (f(S,S)+f (S,T))-(f(S,S) +f (T,S))= f(S,T)- f(T,S)
所以:f=f(S,T)f(T,S)f= f(S,T)- f(T,S) 定理得證 。
推論一:
  如果f是網絡中的一個流,CUT(S,T)CUT(S,T)是一個割,那麼f的值不超過割CUT(S,T)CUT(S,T)的容量。
  推論二:
  網絡中的最大流不超過任何割的容量。
定理二:
  在網絡中,如果f是一個流,CUT(S,T)CUT (S,T)是一個割,且ff的值等於割CUT(S,T)CUT(S,T)的容量,那麼f是一個最大流, CUT(S,T)CUT(S,T)是一個最小割。
證明:
  令割CUT(S,T)CUT(S,T)的容量爲C,所以流ff的流量也爲CC。假設另外的任意流f1f1,流量爲c1c1,根據流量不超過割的容量,則c1&lt;=cc1&lt;=c,所以ff是最大流。 假設另外的任意割CUT(S1,T1)CUT(S1,T1),容量爲c1c1,根據流量不超過割的容量,所以有c1&gt;=cc1&gt;=c,故,CUT(S,T)CUT(S,T)是最小割。

定理結論

在任何的網絡中,最大流的值等於最小割的容量 [1] 。
結論一:
  最大流時,最小割cut(S,T)中,正向割邊的流量=容量,逆向割邊的流量爲0。
結論二:
  在最小割cut(S,T)中:

  • 源點s屬於S。
  • 如果i屬於S,結點j滿足:
      有弧<i,j>,並且c [i,j] >f [i,j] ,或者有弧<i,j>,並且f [i,j] >0,那麼j屬於S。否則不是最小割。 即從s出發能找到的含有殘留的點組成集合S,其餘的點組成集合T。

(HDU3046)

問題描述
在ZJNU,有一個着名的草原。它吸引了愉快的綿羊和他的同伴去度假。大狼和他的家人都知道這件事,然後悄悄藏在大草坪上。作爲ZJNU ACM / ICPC團隊,我們有義務保護宜人的綿羊和他的同伴,免受大狼的干擾。我們決定建造一些長度爲1的單位圍欄。任何狼和羊都不能越過籬笆。當然,一個網格只能包含動物。
現在,我們要求放置最小的柵欄,讓喜羊羊和他的同伴免受大狼和他的同伴的打擾。
輸入
有很多情況。
對於每種情況:
N和M(N,M <= 200)
然後N * M矩陣:
0是空的,1是愉快的綿羊和他的同伴,2是大狼和他的同伴。
產量
對於每種情況:
第一行輸出“Case p:”,p是第p種情況;
第二行是答案。
樣本輸入
4 6
1 0 0 1 0 0
0 1 1 0 0 0
2 0 0 0 0 0
0 2 0 1 1 0
樣本輸出
Case 1:
4

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 2e5;
const int inf = 0x3f3f3f3f;
int n, m, s, t, T = 0;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int map[210][210];
struct Edge{
    int v, w, next;
}edge[maxn];
int head[maxn], cnt;
int dis[maxn];
bool vis[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(){
    fill(dis, dis+maxn, inf);
    memset(vis, false, sizeof(vis));
    queue<int> q;
    q.push(s);
    dis[s] = 0;
    vis[s] = true;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = head[u]; i != -1; i = edge[i].next){
            int v = edge[i].v;
            if(edge[i].w && dis[v] > dis[u]+1){
                dis[v] = dis[u]+1;
                if(!vis[v]){
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return dis[t]!=inf;
}
int DFS(int u, int flow){
    if(u == t)return flow;
    int maxFlow = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int w, v = edge[i].v;
        if(flow > 0 && dis[u]+1==dis[v] && (w = DFS(v, min(flow, edge[i].w)))){
            maxFlow+=w;
            flow-=w;
            edge[i].w-=w;
            edge[i^1].w+=w;
        }
    }
    return maxFlow;
}
void dinic(){
    int maxFlow = 0;
    while(BFS()){
        int w = DFS(s, inf);
        maxFlow+= w;
    }
    
    printf("Case %d:\n%d\n", ++T,maxFlow);
}
int main(){
    while(scanf("%d%d", &n, &m)!=EOF){
        init();
        s = n*m+1, t = s+1;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                scanf("%d", &map[i][j]);
                if(map[i][j] == 1){
                    add(s, (i-1)*m+j, inf);
                }
                else if(map[i][j] == 2){
                    add((i-1)*m+j, t, inf);
                }
            }
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                for(int k = 0; k < 4; k++){
                    int tx = i+dir[k][0], ty =  j+dir[k][1];
                    if(tx <= 0 || tx > n || ty <= 0 || ty > m) continue;
                    add((i-1)*m+j, (tx-1)*m+ty, 1);
                }
            }
        }
        dinic();
    }
    return 0;
} 

最大權閉合子圖

一個子圖(點集), 如果它的所有的出邊都在這個子圖當中,那麼它就是閉合子圖。
點權和最大的閉合子圖就是最大閉合子圖。

首先我們由一道題來引入,見 [線性規劃與網絡流24題 2] 太空飛行計劃問題 。

這道題中,實驗依賴於儀器,而實驗和儀器都有權值,且儀器爲負,實驗爲正。

這裏閉合圖的概念就很好引出了。在一個圖中,我們選取一些點構成集合,記爲V,且集合中的出邊(即集合中的點的向外連出的弧),所指向的終點(弧頭)也在V中,則我們稱V爲閉合圖。最大權閉合圖即在所有閉合圖中,集合中點的權值之和最大的V,我們稱V爲最大權閉合圖。
在這裏插入圖片描述
上圖中閉合圖有
{5}、{2,5}、{4,5}
{2,4,5}、{3,4,5}
{1,2,3,4,5}、{1,2,4,5}
最大權閉合圖爲{3,4,5}。

針對本題而言,我們將實驗與儀器間連一條有向邊,實驗爲起點(弧尾),儀器爲終點(弧頭)。則如果我們選擇一個閉合圖,那麼這個閉合圖中包含的實驗所需要的儀器也最這個閉合圖裏。而最大權閉合圖即爲題目的解。
瞭解了最大權閉合圖的概念,接下來我們就需要知道如何求最大權閉合圖。
首先我們將其轉化爲一個網絡(現在不要問爲什麼,接下來會證明用網絡可以求解)。構造一個源點S,匯點T。我們將S與所有權值爲正的點連一條容量爲其權值的邊,將所有權值爲負的點與T連一條容量爲其權值的絕對值的邊,原來的邊將其容量定爲正無窮。
在這裏插入圖片描述
上上圖即被轉化爲如上圖網絡。
首先引入結論,最小割所產生的兩個集合中,其源點S所在集合(除去S)爲最大權閉合圖,接下來我們來說明一些結論。

  • 證明:最小割爲簡單割。
    引入一下簡單割的概念:割集的每條邊都與S或T關聯。(請下面閱讀時一定分清最小割與簡單割,容易混淆)
    那麼爲什麼最小割是簡單割呢?因爲除S和T之外的點間的邊的容量是正無窮,最小割的容量不可能爲正無窮。所以,得證。

  • 證明網絡中的簡單割與原圖中閉合圖存在一一對應的關係。(即所有閉合圖都是簡單割,簡單割也必定是一個閉合圖)。
    證明閉合圖是簡單割:如果閉合圖不是簡單割(反證法)。那麼說明有一條邊是容量爲正無窮的邊,則說明閉合圖中有一條出邊的終點不在閉合圖中,矛盾。
    證明簡單割是閉合圖:因爲簡單割不含正無窮的邊,所以不含有連向另一個集合(除T)的點,所以其出邊的終點都在簡單割中,滿足閉合圖定義。得正。

  • 證明最小割所產生的兩個集合中,其源點S所在集合(除去S)爲最大權閉合圖。
    首先我們記一個簡單割的容量爲C,且S所在集合爲N,T所在集合爲M。
    則C=M中所有權值爲正的點的權值(即S與M中點相連的邊的容量)+N中所有權值爲負的點權值的絕對值(即N中點與T中點相連邊的容量)。記(C=x1+y1);(很好理解,不理解畫一個圖或想象一下就明白了)。
    我們記N這個閉合圖的權值和爲W。
    則W=N中權值爲正的點的權值-N中權值爲負的點的權值的絕對值。記(W=x2-y2);
    則W+C=x1+y1+x2-y2。
    因爲明顯y1=y2,所以W+C=x1+x2;
    x1爲M中所有權值爲正的點的權值,x2爲N中權值爲正的點的權值。
    所以x1+x2=所有權值爲正的點的權值之和(記爲TOT).
    所以我們得到W+C=TOT.整理一下W=TOT-C.
    到這裏我們就得到了閉合圖的權值與簡單割的容量的關係。
    因爲TOT爲定值,所以我們欲使W最大,即C最小,即此時這個簡單割爲最小割,此時閉合圖爲其源點S所在集合(除去S)。得正。

至此,我們就將最大權閉合圖問題轉化爲了求最小割的問題。求最小割用最小割容量=最大流,即可將問題轉化爲求最大流的問題。
(HDU1565)

Problem Description
給你一個nn的格子的棋盤,每個格子裏面有一個非負數。
從中取出若干個數,使得任意的兩個數所在的格子沒有公共邊,就是說所取的數所在的2個格子不能相鄰,並且取出的數的和最大。
Input
包括多個測試實例,每個測試實例包括一個整數n 和n
n個非負數(n<=20)
Output
對於每個測試實例,輸出可能取得的最大的和
Sample Input
3
75 15 21
75 15 28
34 70 5
Sample Output
188

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 2e5;
const int inf = 0x3f3f3f3f;
int n, m, s, t, T = 0;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int map[210][210];
struct Edge{
    int v, w, next;
}edge[maxn];
int head[maxn], cnt;
int dis[maxn];
bool vis[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(){
    fill(dis, dis+maxn, inf);
    memset(vis, false, sizeof(vis));
    queue<int> q;
    q.push(s);
    dis[s] = 0;
    vis[s] = true;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = head[u]; i != -1; i = edge[i].next){
            int v = edge[i].v;
            if(edge[i].w && dis[v] > dis[u]+1){
                dis[v] = dis[u]+1;
                if(!vis[v]){
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return dis[t]!=inf;
}
int DFS(int u, int flow){
    if(u == t)return flow;
    int maxFlow = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int w, v = edge[i].v;
        if(flow > 0 && dis[u]+1==dis[v] && (w = DFS(v, min(flow, edge[i].w)))){
            maxFlow+=w;
            flow-=w;
            edge[i].w-=w;
            edge[i^1].w+=w;
        }
    }
    return maxFlow;
}
int dinic(){
    int maxFlow = 0;
    while(BFS()){
        int w = DFS(s, inf);
        maxFlow+= w;
    }
    return maxFlow;
}
int main(){
    while(scanf("%d", &n)!=EOF){
        init();
        int sum = 0;
        s = n*n+1, t = s+1;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                scanf("%d", &map[i][j]);
                sum+=map[i][j];
                if((i+j) % 2 == 0){
                    add(s, (i-1)*n+j, map[i][j]);
                }
                else{
                    add((i-1)*n+j, t, map[i][j]);
                }
            }
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                if ((i + j) % 2 == 0){
                    for(int k = 0; k < 4; k++){
                        int tx = i+dir[k][0], ty =  j+dir[k][1];
                        if(tx <= 0 || tx > n || ty <= 0 || ty > n) continue;
                        add((i-1)*n+j, (tx-1)*n+ty, inf);
                    }
                }
            }
        }
        printf("%d\n", sum-dinic());
    }
    return 0;
} 

有上下界網絡流

問題模型:

給定一個加權的有向圖,滿足:
(1)容量限制條件:b(u,v)f(u,v)c(u,v)b(u, v)\leq f(u, v) \leq c(u,v)
(2)流量平衡條件:(u,w)f(u,w)=(w,u)Ef(w,v)\sum_{(u,w)}f(u,w) = \sum_{(w,u)\in E}f(w,v)

  • (2)中的wV(s,t)w\in V-(s,t) 即除了源匯外,所有點都滿足流量平衡條件,則稱G爲有源匯網絡;否則,即不存在源匯,所有點都滿足流量平衡條件,則稱G爲無源匯網絡。

將這類問題由易到難一一解決:

問題[1] 求無源匯的網絡有上下界的可行流

給定條件:
一個網絡中無源匯點,且每條邊的流量滿足給定特定條件下的上下界限制。
求任意一個滿足條件的可行流,給出可行流中每條邊的流量大小。
現在只需求出任意滿足以上條件的流,即爲該網絡的可行流。

在這裏插入圖片描述
由於下界是一條弧上的流必需要滿足的確定值。下面引入必要弧的概念:必要弧是一定流要滿的弧。必要弧的構造,將容量下界的限制分離開了,從而構造了一個沒有下界的網絡G’:
1. 將原弧(u,v)分離出一條必要弧:c(u,v)=b(u,v)c`(u,v)=b(u,v)。(紅色表示)
2. 原弧:c(u,v)=c(u,v)b(u,v)c`(u,v) = c(u,v)-b(u,v)
在這裏插入圖片描述
由於必要弧的有一定要滿的限制,將必要弧“拉”出來集中考慮:
在這裏插入圖片描述
添加附加源x, 附加匯y。想像一條不限上界的(y, x),用必要弧將它們“串”起來,即對於有向必要弧(u, v),添加(u, y),(x, v),容量爲必要弧容量。這樣就建立了一個等價的網絡。
在這裏插入圖片描述
一個無源匯網絡的可行流的方案一定是必要弧是滿的。若去掉(y, x)後,附加源x到附加匯y的最大流,能使得x的出弧或者y的入弧都滿,充要於原圖有可行流。
在這裏插入圖片描述
算法:

  1. 按上述方法構造新網絡(分離必要弧,附加源匯)
  2. 求附加源x到附加匯y的最大流
  3. 若x的出弧或y的入弧都滿,則有解,將必要弧合併回原圖;否則,無解。
  4. 根據其殘留網絡,對每條管子的剩餘,最大容量Cij-殘餘ij就是當前管子的實際流量。

(SGU 194)

Problem Description
由一位着名的國際恐怖分子本·布拉登領導的恐怖組織正在建造一座核反應堆,爲他們計劃製造的核彈生產鈈。作爲這個羣體中邪惡的計算機天才,您負責開發反應堆的冷卻系統。
反應堆的冷卻系統由特殊冷卻液流過的管道數量組成。管道連接在特殊點,稱爲節點,每個管道都有起始節點和終點。液體必須通過管道從其起始點流向其終點而不是沿相反方向流動。
讓節點從1到N編號。冷卻系統必須設計成液體通過管道循環,進入每個節點的液體量(以時間爲單位)等於離開的液體量節點。也就是說,如果我們將管道從第i個節點到第j個指定的液體量指定爲fijf_{ij},( 如果沒有從節點i到節點j的管道,則將fijf_{ij} = 0),對於每個i,必須符合以下條件:
j=1Nfij=j=1Nfji\sum_{j = 1}^Nf_{ij}= \sum_{j = 1}^Nf_{ji}
每個管道有一定的有限的容量,因此對於每個i和j由配管連接,必須爲fijcijf_{ij} ≤c_{ij}其中cijc_{ij} 是管的容量。爲了提供足夠的冷卻,通過管道從第i個要j個節點中流動的液體的量必須至少爲lijl_{ij},從而它必須是fijlijf_{ij} ≥l_{ij}
給定 所有管道的cijc_{ij}lijl_{ij},找到量fijf_{ij},滿足上面指定的條件。
輸入格式:
輸入文件的第一行包含數字N(1≤N≤200) - 節點數和M - 管道數。以下M行每個包含四個整數–i,j,lijl_{ij}cijc_{ij} 。有至多一個管連接的任意兩個節點和0lijcij1050≤l_{ij} ≤c_{ij} ≤10^5 對所有管道。沒有管道將節點連接到自身。如果存在從第i個節點到第j個節點的管道,則沒有從第j個節點到第i個節點的管道。
輸出格式:
如果有方法進行反應器冷卻,則在輸出文件的第一行打印YES,如果沒有,則打印NO。在第一種情況下,M個整數必須跟隨,第k個數字是第k個管道流動的液體量。管道的編號與輸入文件中的編號相同。
input
4 6
1 2 1 3
2 3 1 3
3 4 1 3
4 1 1 3
1 3 1 3
4 2 1 3
output
YES
1
2
3
2
1
1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue> 
using namespace std;
inline int read(){
    int t=1,num=0;
    char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')t=-1;c=getchar();}
    while(c>='0'&&c<='9'){num=num*10+c-'0';c=getchar();}
    return num*t;
}
const int inf = 0x3f3f3f3f;
const int maxn = 1e5;
const int maxm = 500;
int n, m, s, t;
struct Edge{
	int v, w, c, next;
}edge[maxn];
int head[maxm], cnt;
int dis[maxm];
int in[maxm];
void init(){
	cnt = 0;
	memset(head, -1, sizeof(head));
	memset(in, 0, sizeof(in));
}
void addEdge(int u, int v, int w, int c){
	edge[cnt].v = v;
	edge[cnt].w = w;
	edge[cnt].c = c;
	edge[cnt].next = head[u];
	head[u] = cnt++;
}
void add(int u, int v, int w, int c){
	addEdge(u, v, w, c);
	addEdge(v, u, 0, c);
}
bool BFS(){
	memset(dis, -1, sizeof(dis));
	queue<int> que;
	que.push(s);
	dis[s] = 0;
	while(!que.empty()){
		int u = que.front();
		que.pop();
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].v;
			if(dis[v]==-1 && edge[i].w){
				dis[v] = dis[u]+1;
				que.push(v);
			}
		}
	}
	return dis[t] != -1;
}
int DFS(int u, int flow){
	if(u == t) 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 && (w = DFS(v, min(flow, edge[i].w)))){
			edge[i].w-=w;
			edge[i^1].w+=w;
			return w;
		}
	}
	dis[u] = -1;
	return 0;
}
int dinic(){
	int maxFlow = 0, w;
	while(BFS()){
		while(1){
			w = DFS(s, inf);
			if(w==0)break;
			maxFlow += w;
		}
	}
	return maxFlow;
}
int main(){
	int T;
	T = read();
	while(T--){
		init();
		n = read(), m = read();
		s = n+1, t = s+1;
		int sum = 0;
		int idx1 = cnt;
		for(int i = 1; i <= m; i++){
			int u, v, l, r;
			u = read(), v = read(), l = read(), r = read();
			add(u, v, r-l, r);
			in[v] += l;
			in[u] -= l;
		}
		int idx2 = cnt;
		for(int i = 1; i < s; i++){
			if(in[i] > 0) add(s, i, in[i], inf);
			else if(in[i] < 0){
				add(i, t, -in[i], inf);
				sum+=-in[i];
			}
		}
		int ans = dinic();
		if(ans != sum){
			printf("NO\n");
		}
		else{
			printf("YES\n");
            for(int i=idx1;i<idx2;i+=2){
            	printf("%d\n",edge[i].c-edge[i].w);
			}
		}
	}
	return 0;
} 

問題[2] 求有源匯的網絡有上下界的可行流

給定條件:
一個網絡中存在源匯點S,T,且每條邊的流量滿足給定特定條件下的上下界限制。
求任意一個滿足條件的可行流。給出可行流中每條邊的流量,以及網絡STS\rightarrow T總流量。

如果能把有源匯轉爲無源匯,迴歸到上一個問題,是不是就簡單很多了呢?
只需要加一條從T到S的流量上限爲無窮大的邊即可。如果你已經掌握了上一個問題的解決方法,就可以輕鬆的解決了。
轉化爲無源匯點可行流後,每個點是流量守恆的,對於S,T同樣成立。那麼除了可以求解原網絡上每條邊的流量大小外,還可以通過記錄T到S的流入量來給出原有源匯點的網絡總流量。因爲總流量爲outs,而無源匯圖中ins = outs,而S作爲入度僅連接了TST\rightarrow S這一條邊,那麼這一條有向邊的反向邊上的flow值自然就等於原圖的總流量。

問題[3]求有源匯的網絡有上下界的最大流

算法:

  1. 先轉化爲問題[2]來求解一個可行流。若可行無解,則退出。由於必要弧是分離出來的,所以就可以把必要弧(附加源匯及其臨邊)及其上的流,暫時刪去。再將(T,S)刪去,恢復源匯。
  2. 再次,從S到T找增廣軌,求最大流。
  3. 最後將暫時刪去的下界信息恢復,合併到當前圖中。輸出解。

(ZOJ3229)

Gensokyo is a world which exists quietly beside ours, separated by a mystical border. It is a utopia where humans and other beings such as fairies, youkai(phantoms), and gods live peacefully together. Shameimaru Aya is a crow tengu with the ability to manipulate wind who has been in Gensokyo for over 1000 years. She runs the Bunbunmaru News - a newspaper chock-full of rumors, and owns the Bunkachou - her record of interesting observations for Bunbunmaru News articles and pictures of beautiful danmaku(barrange) or cute girls living in Gensokyo. She is the biggest connoisseur of rumors about the girls of Gensokyo among the tengu. Her intelligence gathering abilities are the best in Gensokyo!
During the coming n days, Aya is planning to take many photos of m cute girls living in Gensokyo to write Bunbunmaru News daily and record at least Gx photos of girl x in total in the Bunkachou. At the k-th day, there are Ck targets, Tk1, Tk2, …, TkCk. The number of photos of target Tki that Aya takes should be in range [Lki, Rki], if less, Aya cannot write an interesting article, if more, the girl will become angry and use her last spell card to attack Aya. What’s more, Aya cannot take more than Dk photos at the k-th day. Under these constraints, the more photos, the better.
Aya is not good at solving this complex problem. So she comes to you, an earthling, for help.
Input
There are about 40 cases. Process to the end of file.
Each case begins with two integers 1 <= n <= 365, 1 <= m <= 1000. Then m integers, G1, G2, …, Gm in range [0, 10000]. Then n days. Each day begins with two integer 1 <= C <= 100, 0 <= D <= 30000. Then C different targets. Each target is described by three integers, 0 <= T < m, 0 <= L <= R <= 100.
Output
For each case, first output the number of photos Aya can take, -1 if it’s impossible to satisfy her needing. If there is a best strategy, output the number of photos of each girl Aya should take at each day on separate lines. The output must be in the same order as the input. If there are more than one best strategy, any one will be OK.
Output a blank line after each case.
Sample Input
2 3
12 12 12
3 18
0 3 9
1 3 9
2 3 9
3 18
0 0 3
1 3 6
2 6 9
Sample Output
36
9
6
3
3
6
9

題意:
給定N天,有M個女孩要上報紙。每個人至少被拍Gi張照片。
每天拍照總數不超過Di。
每天要給Ci個女孩拍照,每個女孩拍照的張數限定在[L, R]內。
求最多的照片總數。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int inf = 1<<30;
const int maxn = 1e6;
const int maxm = 2000;
int n, m, s, t, G;
struct Edge{
	int v, w, next;
}edge[maxn];
int head[maxm], cnt;
int dis[maxm];
int in[maxm],low[555][1111],flag[555][1111];
void init(){
	cnt = 0;
	memset(head, -1, sizeof(head));
	memset(in, 0, sizeof(in));
	memset(low, 0, sizeof(low));
	memset(flag, 0, sizeof(flag));
}
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> que;
	que.push(s);
	dis[s] = 0;
	while(!que.empty()){
		int u = que.front();
		que.pop();
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].v;
			if(dis[v]==-1 && edge[i].w){
				dis[v] = dis[u]+1;
				que.push(v);
			}
		}
	}
	return dis[t] != -1;
}
int DFS(int u, int flow){
	if(u == t) 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 && (w = DFS(v, min(flow, edge[i].w)))){
			edge[i].w-=w;
			edge[i^1].w+=w;
			return w;
		}
	}
	dis[u] = -1;
	return 0;
}
int dinic(){
	int maxFlow = 0, w;
	while(BFS()){
		while(1){
			w = DFS(s, inf);
			if(w==0)break;
			maxFlow += w;
		}
	}
	return maxFlow;
}
int main(){
	while(~scanf("%d%d", &n, &m)){
		int x, y, sum;
		init();
		s = n+m+1;
		t = s+1;
		for(int i = 1; i <= m; i++){
			int d;
			scanf("%d", &d);
			add(n+i, t, inf-d);
			in[t] += d;
			in[n+i] -= d;
		}
		for(int i = 1; i <= n; i++){
			int c, G;
			scanf("%d%d", &c, &G);
			add(s, i, G);
			for(int j = 1; j <= c; j++){
				int k, l, r;
				scanf("%d%d%d", &k, &l, &r);
				add(i, n+k+1, r-l);
				in[n+k+1] += l;
				in[i] -= l;
				low[i][k+1]=l;
				flag[i][k+1]=cnt-2;
			}
		}
		x = t+1, y = t+2, sum = 0;
		for(int i = 1; i <= t; i++){
			if(in[i] > 0){
				add(x, i, in[i]);
				sum+=in[i];
			}
			else if(in[i] < 0){
				add(i, y, -in[i]);
			}
		}
		add(t, s, inf);
		s = x, t = y;
		int ans = dinic();
		if(ans!=sum){
			printf("-1\n");
		}
		else{
			s = n+m+1;
			t = s+1;
			ans=dinic();
			printf("%d\n", ans);
			for(int i = 1 ; i <= n ; i++){
		        for(int j = 1 ; j <= m ; j++){
		         	if(flag[i][j]){
		         	 	printf("%d\n",edge[flag[i][j]^1].w+low[i][j]);
		         	}
		        }
		    }
		}
		puts("");
	}
	return 0;
} 

問題[4]求有源匯的網絡有上下界的最小流

給定條件:
一個網絡中存在源匯點S,T,且每條邊的流量滿足給定特定條件下的上下界限制。
求滿足給定條件的S−>T 最小流。

分析:

  • 增加超級源點st和超級匯點sd,對於有上下界的邊(i,j)流量(L,R)變爲R-L,然後i與sd連接容量是L,st與j連接容量是L;網絡中規定不能有流量流入st,也不能有流量流入sd;
  • 做一次最大流Dinic;
  • 在匯點sd到st連一條容量是inf的邊;
  • 在做一次最大流Dinic
  • 當且僅當附加弧都滿流是有可行流,最後的最小流是flow[sd->st]^1],st到sd的最大流就是sd到st的最小流;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章