1. 程式人生 > >網路流入門之最大流問題(1)

網路流入門之最大流問題(1)

changxv大佬說網路流非常萬能,幾乎可以解決所有與圖有關的題目(網路流的強悍!),這裡寫出部落格只是談談我自己的心得體會,幫助那些看到網路流望而卻步的同學們以及我自己有個大致瞭解。

概念

在一張帶權圖G中,我們已知源點s與匯點t,假設我們要將水從s輸送到t,其間必然會通過一些邊以及其它中轉點(邊就像是水管,邊權就是這個水管的最大容量即最大運送的水量),最大流問題就是要我們求在每個水管最大容量內最多能將多少水從s送到t。

變數定義:

對於每一條邊,我們定義它的上限為容量cap(u,v),實際運送水量稱為流量flow(u,v)

struct edge{

    int from,to,cap,flow;//一般只用一個cap即可(看到後面就知道啦大笑

}

注意:從u->v的水管送2單位水,再從v->u的水管送4單位水沒有意義,因為其等價於從v->u送(4-2=2)單位水。因此,對於每一條從u->v的邊,我們建立一條正向邊(正向弧)和一條反向邊(反向弧),規定其中flow(u,v)>=0,flow(v,u)<=0。

最大流性質:

除了源點s與匯點t,其餘結點都為中轉點,所以有:

1、s的出水流量等於t的入水流量

2、flow(u,v)=-flow(v,u)

3、任意中轉結點的總入水流量等於-總出水流量,即:正向弧flow+反向弧flow=0

增廣路演算法:

即是求解最大流的演算法,演算法思想很簡單,即從零開始不斷增加流量,並且每次都保持上述3條最大流性質。

如:對於一條邊(u,v),若我們要向這條邊送d單位的水量,則flow(u,v)+=d,flow(v,u)-=d。

我們每一次增廣後的圖稱為殘量網路,對於flow=cap的邊,我們稱它已滿流,則在下一次增廣中這條邊是不能流動的(也可以說刪除了這條邊)。

顯然,每次增廣之後流量始終滿足3條性質,所以只要殘量網路中不存在增廣路(s和t不連通),則當前流就是最大流。

由此可以看出我們每次都是找一條任意從s到t的可行路徑,我們很容易想出dfs或bfs暴力演算法,事實上我學的也就是dfs。

舉個例子:假設有n個教室m個老師,第i個老師能在一些教室上課,現在我們讓第一個老師選擇他的教室,當第二個老師來的時候,他選擇他的教室……直到一個老師a選定的一個教室已經被老師b佔用,老師a會選擇與老師b商量,那麼老師b會尋找他的第二個教室,如果此時教室為空,則成功;如果此時教室被老師c佔用,則老師c會去尋找其它教室……這樣,如果最終老師x找到了空教室,則成功,否則,老師a會選擇下一個教室進行嘗試,直到找到空教室,則成功,否則失敗,老師a不能上課。

再給個樣例(畫得醜別噴我。。。):

看懂過程了嗎(省略的權值為零的邊)?我們引入反向邊就是為了防止增廣路演算法演變為貪心演算法,上文已經提到,從u往v送5單位水再從v向u送3單位水,等價於u向v送2單位水,那麼,我們在1->2->3->4中送了4噸水之後,從1->3的過程中,實際上是flow(1,3)的4單位水代替了原先flow(1,2)的水,而原來的水轉而去尋找其他管道增廣,最後加上找到增廣路等最小值d,是不是很好懂了呢?

模板:

介紹3個模板,edmondskarp,dinic和sap。

edmondskarp足以應對不刁鑽的資料,但在大資料中會稍顯無力,就我個人而言,其實還是推薦讀一讀鍛鍊程式碼能力的。

Edmondskarp演算法:

#include<bits/stdc++.h>
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e6+10;
struct edge{
	int from,to,cap,flow;
};
struct EdmondsKarp{
	int n,m,s,t;
	int a[N],p[N];
	vector<int>G[N];
	vector<edge>edges;
	inline void add(int u,int v,int cap){
		edges.push_back((edge){u,v,cap,0});
		edges.push_back((edge){v,u,0,0});
		int sz=edges.size();
		G[u].push_back(sz-2);
		G[v].push_back(sz-1);
	}
	inline void init(){
		n=get(),m=get(),s=get(),t=get();
		while(m--){
			int u=get(),v=get(),cap=get();
			add(u,v,cap);
		}
	}
	inline void solv(){
		int flow=0;
		for(;;){
			memset(a,0,sizeof(a));//a有vis和記錄每一次最小流量的作用
			queue<int>Q;
			Q.push(s);
			a[s]=1<<30;
			while(!Q.empty()){
				int x=Q.front();Q.pop();
				int sz=G[x].size();
				for(int i=0;i<sz;i++){
					edge e=edges[G[x][i]];
					if(!a[e.to]&&e.cap>e.flow){
						a[e.to]=min(a[x],e.cap-e.flow);//增廣當然是取最小啦~ 
						p[e.to]=G[x][i];//記錄
						Q.push(e.to);
					}
				}if(a[t])break;
			}if(!a[t])break;
			for(int u=t;u!=s;u=edges[p[u]].from){
				edges[p[u]].flow+=a[t];
				edges[p[u]^1].flow-=a[t];
			}flow+=a[t];
		}cout<<flow<<"\n";
	}
}z;
int main(){
	z.init();
	z.solv();
	return 0;

}

上述演算法在loj最大流那道模板題就gg了……所以我們需要更快的更優秀的演算法!!!

Dinic演算法:

#include<bits/stdc++.h>
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e5+10;
struct edge{
	int from,to,cap;//有些題可以忽略from 
};
struct Dinic{
	int n,m,s,t;
	int d[N],cur[N];//cur陣列是個小優化 
	vector<int>G[N];
	vector<edge>edges;
	inline void add(int u,int v,int cap){
		edges.push_back((edge){u,v,cap});
		edges.push_back((edge){v,u,0});
		int sz=edges.size();
		G[u].push_back(sz-2);
		G[v].push_back(sz-1);
	}
	inline void init(){
		n=get(),m=get(),s=get(),t=get();
		while(m--){
			int u=get(),v=get(),cap=get();
			add(u,v,cap);
		}
	}
	inline bool bfs(){//相當於刪邊,重新構圖
		memset(d,-1,sizeof(d));
		queue<int>Q;
		Q.push(s);
		d[s]=0;
		while(!Q.empty()){
			int x=Q.front();Q.pop();
			int sz=G[x].size();
			for(int i=0;i<sz;i++){
				edge e=edges[G[x][i]];
				if(d[e.to]==-1&&e.cap>0){//因為每一次增廣後有些邊阻塞,但直接刪除邊會很麻煩,而且直接刪邊是貪心的錯誤解法 
					d[e.to]=d[x]+1;
					Q.push(e.to);
				}
			}
		}return d[t]!=-1;//是否存在增光路 
	}
	inline int dfs(int x,int Maxf){
		if(x==t||Maxf==0)return Maxf;
		int f,ret=0,sz=G[x].size();
		for(int& i=cur[x];i<sz;i++){
			edge &e=edges[G[x][i]];
			if(d[e.to]==d[x]+1&&(f=dfs(e.to,min(Maxf,e.cap)))>0){
				edges[G[x][i]^1].cap+=f;//由於我們直接走的cap,沒有使用flow,所以這些操作……嗯,你們懂的 
				e.cap-=f;
				Maxf-=f;
				ret+=f;
				if(Maxf==0)break;
			}
		}return ret;//顯然是要求這條路徑中容量最小的一條的權值,不然這個最小的水管會爆掉 
	}
	inline void solv(){
		int flow=0;
		while(bfs()){
			memset(cur,0,sizeof(cur));
			flow+=dfs(s,1<<30);//累加每一次增廣的流量 
		}cout<<flow<<"\n";
	}
}z;
int main(){
	z.init();
	z.solv();
	return 0;
}

注意在dinic演算法中我們每次跑最大流都要重新標號,這顯然是可以優化的,還有一個更優秀的sap演算法(Gap優化):

Sap演算法:

#include<bits/stdc++.h>
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e6+10;
struct edge{
	int to,cap;
};
struct SAP{
	int n,m,s,t;
	int d[N],cur[N],Gap[N];
	vector<int>G[N];
	vector<edge>edges;
	inline void add(int u,int v,int cap){
		edges.push_back((edge){v,cap});
		edges.push_back((edge){u,0});
		int sz=edges.size();
		G[u].push_back(sz-2);
		G[v].push_back(sz-1);
	}
	inline void init(){
		n=get(),m=get(),s=get(),t=get();
		while(m--){
			int u=get(),v=get(),cap=get();
			add(u,v,cap);
		}
	}
	inline int dfs(int x,int Maxf){
		if(x==t||Maxf==0)return Maxf;
		int f,ret=0,sz=G[x].size();
		for(int& i=cur[x];i<sz;i++){
			edge &e=edges[G[x][i]];
			if(d[e.to]==d[x]-1&&(f=dfs(e.to,min(Maxf,e.cap)))>0){
				edges[G[x][i]^1].cap+=f;
				e.cap-=f;
				Maxf-=f;
				ret+=f;
				if(d[s]==n||Maxf==0)return ret;
			}
		}
		if(--Gap[d[x]]==0)d[s]=n;
		++Gap[++d[x]];
		return ret;
	}
	inline void solv(){
		int flow=0;
		Gap[0]=n;
		while(d[s]<n){
			memset(cur,0,sizeof(cur));
			flow+=dfs(s,1<<30);
		}cout<<flow<<"\n";
	}
}z;
int main(){
	z.init();
	z.solv();
	return 0;
}

可以根據自己的喜好選擇dinic或者sap,當然dinic更好寫啦,而且實際表現也不必sap差多少,我就是寫的dinic~

例題詳見網路流入門之建模(2)

ps:vector在大資料會比鄰接錶快,因此,小資料t不掉,大資料你用vector肯定不比sap鄰接表差啊,出題人怎麼會用vector來卡點呢?(不信的話可以在loj上的最大流·加強版測試測試)

相關推薦

網路流入問題1

changxv大佬說網路流非常萬能,幾乎可以解決所有與圖有關的題目(網路流的強悍!),這裡寫出部落格只是談談我自己的心得體會,幫助那些看到網路流望而卻步的同學們以及我自己有個大致瞭解。 概念: 在一張帶權圖G中,我們已知源點s與匯點t,假設我們要將水從s輸送到t,其間必然

HDU 3549網路流入

Flow Problem Time Limit: 5000/5000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 10327    Accepted S

網路

在一個網路裡   對於S流到T的流量,有很多值。其中這些值的最大值,我們稱為S到T的最大流 (想象成自來水管,S就是水廠,T就是你家,邊就是管子,最大流就是能流到你家水量的最大值)   介紹三個相關的知識點  (1) 容量網路    &nbs

hdu3549(網路流入題-的Ford-Fulkerson演算法)

網路流深入學習請戳這裡。 Ford-Fulkerson方法依賴於三種重要思想,這三個思想就是:殘留網路,增廣路徑和割。 Ford-Fulkerson方法是一種迭代的方法。開始時,對所有的u,v∈V

網路Dinic演算法

程式碼對應於 POJ - 3281 #include <iostream> #include <cstring> #include <cstdio> #include <queue> #define fuck

網路小費用模板

#include <bits/stdc++.h> using namespace std; const int Max=50010; const int inf=1e9; int n,m,ans1,ans2,size=1,head,tail,s,t; int f

【模板】網路 Dinic

題目描述 給出一個網路圖,以及其源點和匯點,求出其網路最大流。 輸入輸出格式 輸入格式: 第一行包含四個正整數N、M、S、T,分別表示點的個數、有向邊的個數、源點序號、匯點序號。 接下來M行每行包含三個正整數ui、vi、wi,表示第i條有向邊從

網路SAP模板

#include <stdio.h> #include <string.h> const int inf=1<<30; #define MAXM 2000 struct edge { int from, to, val, nex

HDU3081:Marriage Match II (Floyd/並查集+二分圖匹配/+二分)

Marriage Match II Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5469 &n

網路演算法EdmondsKarp

求網路流有很多演算法,這幾天學習了兩種,記錄一下EK演算法。 首先是網路流中的一些定義: V表示整個圖中的所有結點的集合.E表示整個圖中所有邊的集合.G = (V,E) ,表示整個圖.s表示網路的源點

POJ-1273-Drainage Ditches網絡

rom lang spa bsp pen from per int eof Every time it rains on Farmer John‘s fields, a pond forms over Bessie‘s favorite clover patch. This

網路1-------

一.網路流最大流問題和基本概念 1.網路流基本概念 (1)名詞解釋 源點:流量的源頭,只有流出去的點 匯點:流量的匯聚點,只有流進來的點 流量:一條邊上流過的實際流量 容量:一條邊上可供流過的最大流量 殘量:一條邊上的容量-當前流量,剩下可流的最大流 (2)網路流概念

洛谷P4016 負載平衡問題 網路小費用

P4016 思路: 因為是每個倉庫貨物相等,那麼最後肯定是總和/個數(平均值ave)。 建圖: 源點s,匯點t。 如果i倉庫貨物大於平均值,那麼表示需要流出,所以: i -> t 流量為a[i]

Codeforces 513F2 題解 網路- 二分 BFS

Scaygerboss 題目描述 在一個有障礙的網格圖中,有male 個男人和female 個女人,還有一個叫BOSS的人妖(既可以當男人又可以當女人)。這些人分佈在地圖上,每一個cell可以同時有多個人。這些人每個人移動各需要ti 的時間,問最小

hihocoder1378 網路小割

題目連結:http://hihocoder.com/problemset/problem/1378 思路: 描述 小Hi:在上一週的Hiho一下中我們初步講解了網路流的概念以及常規解法,小Ho你還記得內容麼? 小Ho:我記得!網路流就是給定了一張圖G=(V

hihocoder 1369 網路

題目連結:http://hihocoder.com/problemset/problem/1369 思路:每次找一條不停地能到達目的點的路(增廣路),然後更新圖的流量。 ,使用:Ford-Fulkerson演算法 #include <iostream> #in

網路小割記錄路徑【POJ1815】

  【POJ1815】 出處:原帖 題意:就是求s點到t點,最少去掉幾個點使得他們不連通。如果無解輸出NO ANSWER!    解題思路 因為最小割只能求割掉幾條邊的解,我們要求的是割掉幾個點。那麼我們可以這樣考慮:把每個點拆成入點和出點。入點->出點權值為1。那麼

網路】POJ1273-Drainage Ditche【模板題】

這是一道網路流的入門題,用來理解最大流很好。 #include<iostream> #include<string> #include<cstdio> #include<cstring> #include<map&g

網路基礎概念+三個演算法

下面是由一道題引發的一系列故事。。。 Drainage Ditches Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 68920 Accepted: 26683 Description E

kuangbin求帶飛網路 ACM Computer Factory節點型

As you know, all the computers used for ACM contests must be identical, so the participants compete on equal terms. That is why all these computers are hi