轉自:http://comzyh.tk/blog/archives/568/
“網絡流博大精深”—sideman語
感謝WHD的大力支持
最早知道網絡流的內容便是最大流問題,最大流問題很好理解:
解釋一定要通俗!
如右圖所示,有一個管道系統,節點{1,2,3,4},有向管道{A,B,C,D,E},即有向圖一張. [1]是源點,有無限的水量,[4]是匯點,管道容量如圖所示.試問[4]點最大可接收的水的流量?
這便是簡單的最大流問題,顯然[4]點的最大流量為50
死理性派請注意:流量是單位時間內的,總可以了吧!
然而對於復雜圖的最大流方法是什么呢,有EK,Dinic,SAP,etc.下面介紹Dinic算法(看代碼的直接點這)
Dinic 算法
Dinic算法的基本思路:
- 根據殘量網絡計算層次圖。
- 在層次圖中使用DFS進行增廣直到不存在增廣路
- 重復以上步驟直到無法增廣
引自NOCOW,相當簡單是吧...
小貼士:
一般情況下在Dinic算法中,我們只記錄某一邊的剩余流量.
- 殘量網絡:包含反向弧的有向圖,Dinic要循環的,每次修改過的圖都是殘量網絡,
- 層次圖:分層圖,以[從原點到某點的最短距離]分層的圖,距離相等的為一層,(比如上圖的分層為{1},{2,4},{3})
- DFS:這個就不用說了吧...
- 增廣 :在現有流量基礎上發現新的路徑,擴大發現的最大流量(注意:增加量不一定是這條路徑的流量,而是新的流量與上次流量之差)
- 增廣路:在現有流量基礎上發現的新路徑.(快來找茬,和上一條有何不同?)
- 剩余流量:當一條邊被增廣之后(即它是增廣路的一部分,或者說增廣路通過這條邊),這條邊還能通過的流量.
- 反向弧:我們在Dinic算法中,對於一條有向邊,我們需要建立另一條反向邊(弧),當正向(輸入數據)邊剩余流量減少I時,反向弧剩余流量增加I
Comzyh的較詳細解釋(流程) :
Dinic動畫演示
- 用BFS建立分層圖 注意:分層圖是以當前圖為基礎建立的,所以要重復建立分層圖
- 用DFS的方法尋找一條由源點到匯點的路徑,獲得這條路徑的流量I 根據這條路徑修改整個圖,將所經之處正向邊流量減少I,反向邊流量增加I,注意I是非負數
- 重復步驟2,直到DFS找不到新的路徑時,重復步驟1
注意(可以無視):
- Dinic(其實其他的好多)算法中尋找到增廣路后要將反向邊增加I
- Dinic中DFS時只在分層圖中DFS,意思是說DFS的下一個節點的Dis(距源點的距離)要比自己的Dis大1,例如在圖1中第一個次DFS中,1->2->4 這條路徑是不合法的,因為Dis[2]=1;Dis[4]=1;
- 步驟2中"獲得這條路徑的流量I "實現:DFS函數有參量low,代表從源點到現在最窄的(剩余流量最小)的邊的剩余流量,當DFS到匯點是,Low便是我們要的流量I
對於反向弧(反向邊)的理解:
這一段不理解也不是不可以,對於會寫算法沒什么幫助,如果你着急,直接無視即可.
先舉一個例子(如右圖):
在這幅圖中我們首先要增廣1->2->4->6,這時可以獲得一個容量為 2的流,但是如果不建立4->2反向弧的話,則無法進一步增廣,最終答案為2,顯然是不對的,然而如果建立了反向弧4->2,則第二次能進行 1->3->4->2->5->6的增廣,最大流為3.
Comzyh對反向弧的理解可以說是"偷梁換柱",請仔細閱讀: 在上面的例子中,我們可以看出,最終結果是1->2->5->6和1->2->4->6和 1->3->4->6.當增廣完1->2->4->6(代號A)后,在增廣 1->3->4->2->5->6(代號B),相當於將經過節點2的A流從中截流1(總共是2)走2->5>6,而不走2->4>6了,同時B流也從節點4截流出1(總共是1)走4->6而不是4->2->5->6,相當於AB流做加法.
簡單的說反向弧為今后提供反悔的機會,讓前面不走這條路而走別的路.
Dinic算法的程序實現
最大流算法一直有一個入門經典題:POJ 1273 或者是UCACO 4_2_1 來自NOCOW(中文) 這兩個是同一個題
給出這道題的代碼
2 #include<cstring>
3 #include<cmath>
4 #include<iostream>
5 #include<algorithm>
6 #include< set>
7 #include<map>
8 #include<queue>
9 #include<vector>
10 #include< string>
11 #define Min(a,b) a<b?a:b
12 #define Max(a,b) a>b?a:b
13 #define CL(a,num) memset(a,num,sizeof(a));
14 #define eps 1e-12
15 #define inf 0x7fffffff
16
17 // freopen("data.txt","r",stdin);
18 const double pi = acos(- 1.0);
19 typedef __int64 ll;
20 const int maxn = 300 ;
21 using namespace std;
22 int n , m;
23 int flow[maxn][maxn],dis[maxn] ; // dis[i],表示 到 原點 s 的 層數
24
25 int bfs() // 重新 建 圖 (按 層數 建圖)
26 {
27 CL(dis,- 1);
28 dis[ 1] = 0 ;
29 queue< int>que;
30 que.push( 1);
31 while(!que.empty())
32 {
33 int k = que.front();que.pop() ;
34 for( int i = 1;i<= n;i++)
35 {
36 if(flow[k][i] > 0 && dis[i] < 0 ) // 如果 可以 可以到達 但 還沒有 訪問
37 {
38 dis[i] = dis[k]+ 1 ;
39 que.push(i) ;
40 }
41 }
42 }
43
44 if(dis[n] > 0) return 1;
45 else return 0 ;
46
47 }
48 int dfs( int x, int mx) // 查找 路徑上的 最小 的 流量
49 {
50
51 int i , a ;
52 if(x == n) return mx ;
53
54 for(i = 1;i<= n;i++)
55 {
56 if(flow[x][i] > 0 && dis[i] == dis[x] + 1 && (a =dfs(i,min(mx,flow[x][i]))))
57 {
58 flow[x][i] -= a;
59 flow[i][x] += a;
60 return a ;
61
62
63 }
64 }
65 return 0 ;
66 }
67 int main()
68 {
69 // freopen("data.txt","r",stdin);
70 int i ,s,e,c;
71 while(scanf( " %d%d ",&m,&n)!=EOF)
72 {
73 CL(flow, 0);
74 for(i = 0 ; i < m;i++)
75 {
76 scanf( " %d%d%d ",&s,&e,&c);
77 flow[s][e] += c;
78 }
79 int ans = 0;
80 int res;
81
82 while(bfs())
83 {
84
85
86 while(res = dfs( 1,inf)) ans+= res ;
87
88 }
89 printf( " %d\n ",ans);
90 }
91
92 }
更高效的 dinic
hdu 4292 Food
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include< set>
#include<map>
#include<queue>
#include<vector>
#include< string>
#define INF 0x3fffffff
#define F(x) (x)
#define N(x) (205+(x))
#define CPN(x) (410+(x))
#define D(x) (615+(x))
#define maxn 250
#define CL(a,b) memset(a,b,sizeof(a))
#define Pnum 210
using namespace std;
int next[maxn* 20],dis[maxn* 10];
int s,e;
int n, fnum ,dnum ,f[maxn],d[maxn],cnt;
struct node
{
int to;
int cap ;
int next ;
}p[ 200000] ;
int que[maxn*maxn] ,idx;
void add( int x, int y, int cap)// 建邊 注意 反向邊的流量為 0
{
p[cnt].to = y;
p[cnt].cap = cap ;
p[cnt].next = next[x];
next[x] = cnt++ ;
p[cnt].to = x;
p[cnt].cap = 0;
p[cnt].next = next[y];
next[y] = cnt++ ;
}
int bfs() // 重新 建 圖 (按 層數 建圖)
{
memset(dis, 0xff, sizeof(dis)) ;
dis[s] = 0 ;
queue< int>que;
que.push(s);
while(!que.empty())
{
int k = que.front();que.pop() ;
for( int i = next[k];i!=- 1;i = p[i].next)
{
int v = p[i].to;
int cap = p[i].cap ;
if(cap > 0 && dis[v] < 0 ) // 如果 可以 可以到達 但 還沒有 訪問
{
dis[v] = dis[k]+ 1 ;
que.push(v) ;
}
}
}
if(dis[e] > 0) return 1;
else return 0 ;
}
int dfs( int x, int mx) // 查找 路徑上的 最小 的 流量
{
int i , a ,tf = 0;
if(x == e) return mx ;
for(i = next[x];i!= - 1;i = p[i].next)
{
int v = p[i].to ;
int cap = p[i].cap ;
if(cap > 0 && dis[v] == dis[x] + 1 && (a =dfs(v,min(cap,mx))))
{
p[i].cap -= a;
p[i^ 1].cap += a;
return a;
}
}
if(!tf) dis[x] = - 1; // 沒有 找到 最小流量 ,說明 從這個點到不了 終點 ,所以 標記一下
return tf ;
}
int main()
{
int i , j ;
char c[ 250] ;
// freopen("data.txt","r",stdin) ;
while(scanf( " %d%d%d ",&n,&fnum,&dnum)!=EOF)
{
CL(next,- 1) ;
cnt = 0;
s = 0;
e = 2000;
for(i = 1 ; i <= fnum;i++)
{
scanf( " %d ",&f[i]);
}
for(i = 1 ; i<= dnum;i++)
{
scanf( " %d ",&d[i]) ;
}
for(i = 1; i <= n;i++) // 人 和 吃的
{
scanf( " %s ",c);
for(j = 0 ; j< fnum ;j++)
{
if(c[j] == ' Y ')
{
add(j + 1,i + Pnum, 1) ;
}
}
}
for(i = 1; i<= n;i++) // 人 和 喝的
{
scanf( " %s ",c);
for(j = 0 ; j< dnum ;j++)
{
if(c[j] == ' Y ')
{
add(i + Pnum* 2,j + Pnum* 3 + 1, 1) ;
}
}
}
for(i = 1; i <= fnum;i++) // 增加源點
{
add( 0,i,f[i]) ;
}
for(i = Pnum* 3 + 1,j = 1; j <= dnum;i++,j++) // 增加 匯點
{
add(i,e,d[j]) ;
}
for(i = 1; i <= n;i++) // 將人 拆分
{
add(i + Pnum,i +Pnum* 2, 1);
}
int ans = 0;
int res;
while(bfs())
{
while(res = dfs(s,INF)) ans+= res ;
}
printf( " %d\n ",ans);
}
}