1. 程式人生 > >學習筆記:網絡流

學習筆記:網絡流

edge ++ 當前 部分 最簡 註意 9.png 區間 cost

網絡流

網絡流是省選必備算法啊……早就想學了但一直沒空……現在學習一下順便做做筆記

(PS:STO%%%手搓網絡流的bGary大佬)

網址整理:

http://blog.csdn.net/ruoruo_cheng/article/details/51815257 網絡流常見模型

https://www.cnblogs.com/gengchen/p/6605548.html 網絡流24題模型分析

https://baike.baidu.com/item/網絡流 網絡流百度百科

註:本博客可能使用了其他博客的圖或者部分內容,若有未標明的麻煩告訴我一聲,謝謝

什麽是網絡流?

圖論中的一種理論與方法,研究網絡上的一類最優化問題
很多系統中涉及流量問題,例如公路系統中車流量,網絡中的數據信息流,供油管道的油流量等。我們可以將有向圖進一步理解為流網絡”(flow network),並利用這樣的抽象模型求解有關流量的問題

……

看不懂啊餵

算了還是舉栗子好用

技術分享圖片

可以類比水流,從1點註水,從6點接收水。每條路即為一個管道,該管道有一定容量(那我們註入的水肯定不能超過這個容量)。求最大的接水量.。

源點:只出不進的點,即上圖1點

匯點:只進不出的點,即上圖6點

容量

:最大承受的量,即上圖的邊的邊權

流量:實際上流過的量。

最小割:從圖上拆掉幾個邊,使源點和匯點不連通的最小花費

(最大流=最小割)

網絡流之最大流

一、EK算法(必須理解)

n*m^2

求解思路

首先引入一個概念:可行流。即為各邊上的流量都沒超過容量的流。

最簡單的例子:零流。這個一定是一個可行流。

那麽我們就從零流考慮如何找最大流。

(1) 假如有一條源點到匯點的路,路徑上的流量都嚴格小於容量

(2) 那麽我們就可以給這條路徑增加流量。增加的量delta為各邊(容量-流量)中的最小值。

(3) 這樣我們就可以找到一個更大的流了。而在(2)中的那條路,就叫做增廣路

。簡單理解就是流量可以在原先基礎上增加的路徑

(4) 找不到增廣路時,當前的流量就是最大流

那麽問題來了:上面的方法並不是完全正確的(?)比如下面的網絡流模型:

技術分享圖片

第一次找增廣路後就成了下圖

技術分享圖片

這樣的話找一次就沒有增廣路了,求得最大流為1。

但這顯然是錯的。因為走(1-2-4)和(1-3-4)才是正確答案,最大流為2.

很顯然,我們第一次找增廣路的時候將最優的走法堵上了,所以無法求得最優解。

為了解決這個問題,網絡流就有一個特別巧妙的方法解決這個問題:反向邊

反向邊,顧名思義,就是在原有的每條邊上建一條方向相反的邊,初始值為0。

我們用c[x][y]存x點到y點的容量。每一次找到增廣路後,我們在路徑上每一條邊的容量減少delta的同時,給每條反向邊加上delta。

C[x][y]+=delta

C[y][x]-=delta

那上面的例子就可以正確求解了。

一開始找到(1-2-3-4)這條增廣路,修改成如下圖

技術分享圖片

然後再找(1-3-2-4)這條增廣路,修改成下圖,即可得到最大流2

技術分享圖片

簡單理解就是第二次找增廣路的時候我們將正向邊(2-3)的流量利用反向邊“退”了回去。

代碼實現:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<queue>
 6 #define INF (0x7fffffff)
 7 #define MAXN (10000+10)
 8 #define MAXM (100000+10)
 9 using namespace std; 
10 bool visit[MAXN];
11 int pre[MAXN];
12 int n,m,s,t,Ans;
13 int num_edge,head[MAXN];
14 int q[MAXN];
15 struct node
16 {
17     int to;
18     int next;
19     int Flow;//殘留網絡 
20 }edge[MAXM*2];
21 
22 void add(int u,int v,int l)
23 {
24     edge[++num_edge].to=v;
25     edge[num_edge].next=head[u];
26     edge[num_edge].Flow=l;
27     head[u]=num_edge;    
28 }
29 
30 bool Bfs(int s,int t)
31 {
32     int Head=0,Tail=1;
33     memset(pre,-1,sizeof(pre));
34     memset(visit,false,sizeof(visit));
35     visit[s]=true;
36     q[1]=s;
37     while (Head<Tail)
38     {
39         ++Head;
40         int x=q[Head];
41         for (int i=head[x];i!=0;i=edge[i].next)
42             if (edge[i].Flow>0 && !visit[edge[i].to])
43             {
44                 pre[edge[i].to]=i;
45                 visit[edge[i].to]=true;
46                 if (edge[i].to==t) return true;
47                 q[++Tail]=edge[i].to;
48             }
49     }
50     return false;
51 }
52 
53 int EK(int s,int t)
54 {
55     while (Bfs(s,t))
56     {
57         int d=INF;
58         for (int i=t;i!=s;i=edge[((pre[i]-1)^1)+1].to)
59             d=min(d,edge[pre[i]].Flow);
60         for (int i=t;i!=s;i=edge[((pre[i]-1)^1)+1].to)
61         {
62             edge[pre[i]].Flow-=d;
63             edge[((pre[i]-1)^1)+1].Flow+=d;
64         }
65         Ans+=d;
66     }
67     return Ans;
68 }
69 
70 int main()
71 {
72     int u,v,l;
73     scanf("%d%d%d%d",&n,&m,&s,&t);
74     for (int i=1;i<=m;++i)
75     {
76         
77         scanf("%d%d%d",&u,&v,&l);
78         add(u,v,l);
79         add(v,u,0);
80     }
81     printf("%d",EK(s,t));
82 }

二、Dinic算法

n^2*m

求解思路

只要理解EK,Dinic就很好理解了。在EK中,每找一條增廣路就要BFS一次,然後重新BFS,這樣的時間復雜度肯定是很大的。這裏我們用一個技巧來優化:分層圖

技術分享圖片

每個點的層次即為其到源點的最短距離,BFS實現。

每次我們BFS構建分層圖。如果BFS後發現有增廣路,那麽我們DFS在這個分層圖上找所有可行增廣路(多路增廣)。

註意:DFS時只能往比自己深1的點走。

代碼實現:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #define MAXM (100000+10)
 6 #define MAXN (10000+10)
 7 using namespace std;
 8 struct node
 9 {
10     int Flow;
11     int next;
12     int to;
13 } edge[MAXM*2];
14 int Depth[MAXN],q[MAXN];
15 int head[MAXN],num_edge;
16 int n,m,s,e;
17 
18 void add(int u,int v,int l)
19 {
20     edge[++num_edge].to=v;
21     edge[num_edge].Flow=l;
22     edge[num_edge].next=head[u];
23     head[u]=num_edge;
24 }
25 
26 bool Bfs(int s,int e)
27 {
28     int Head=0,Tail=1;
29     memset(Depth,0,sizeof(Depth));
30     Depth[s]=1;
31     q[1]=s;
32     while (Head<Tail)
33     {
34         ++Head;
35         int x=q[Head];
36         for (int i=head[x]; i!=0; i=edge[i].next)
37             if (!Depth[edge[i].to] && edge[i].Flow>0)
38             {
39                 Depth[edge[i].to]=Depth[x]+1;
40                 q[++Tail]=edge[i].to;
41             }
42     }
43     if (Depth[e]>0) return true;
44     return false;
45 }
46 
47 int Dfs(int x,int low)
48 {
49     int Min,f=0;
50     if (x==e || low==0)
51         return low;
52     for (int i=head[x]; i!=0; i=edge[i].next)
53         if (edge[i].Flow>0 && Depth[edge[i].to]==Depth[x]+1 && (Min=Dfs(edge[i].to , min(low,edge[i].Flow) )))
54         {
55             edge[i].Flow-=Min;
56             edge[((i-1)^1)+1].Flow+=Min;
57             f+=Min;
58             low-=Min;
59         }
60     return f;
61 }
62 
63 int Dinic(int s,int e)
64 {
65     int Ans=0,Road;
66     while (Bfs(s,e))
67         Ans+=Dfs(s,0x7fffffff);
68     return Ans;
69 }
70 
71 int main()
72 {
73     int u,v,l;
74     scanf("%d%d%d%d",&n,&m,&s,&e);
75     for (int i=1; i<=m; ++i)
76     {
77         scanf("%d%d%d",&u,&v,&l);
78         add(u,v,l);
79         add(v,u,0);
80     }
81     printf("%d",Dinic(s,e));
82 }

網絡流之費用流

一、 EK+SPFA

恩……這個還是需要EK+貪心的思想。

每次找一個最小花費的增廣路,然後增廣。可以證明當前費用為達到當前流量的最小花費。

至於找最小花費的增廣路,用SPFA替換EK裏的BFS就可以了。

註意:建邊時反向邊的花費要設置成正向邊的相反數。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #define MAXN (5000+10)
 5 #define MAXM (50000+10)
 6 using namespace std;
 7 int pre[MAXN],n,m,s,e,Ans,num_edge,head[MAXN],q[MAXN*100];
 8 int dis[MAXN],INF,u,v,l,c;
 9 bool used[MAXN];
10 struct node
11 {
12     int to;
13     int next;
14     int Flow;//殘留網絡
15     int Cost;
16 } edge[MAXM*2];
17 
18 void add(int u,int v,int l,int c)
19 {
20     edge[++num_edge].to=v;
21     edge[num_edge].next=head[u];
22     edge[num_edge].Flow=l;
23     edge[num_edge].Cost=c;
24     head[u]=num_edge;
25 }
26 
27 bool Spfa(int s,int e)
28 {
29     int Head=0,Tail=1;
30     memset(pre,-1,sizeof(pre));
31     memset(dis,0x7f,sizeof(dis));
32     q[1]=s;
33     dis[s]=0;
34     used[s]=true;
35     while (Head<Tail)
36     {
37         int x=q[++Head];
38         for (int i=head[x]; i!=0; i=edge[i].next)
39             if (dis[x]+edge[i].Cost<dis[edge[i].to] && edge[i].Flow>0)
40             {
41                 dis[edge[i].to]=edge[i].Cost+dis[x];
42                 pre[edge[i].to]=i;
43                 if (!used[edge[i].to])
44                 {
45                     used[edge[i].to]=true;
46                     q[++Tail]=edge[i].to;
47                 }
48             }
49         used[x]=false;
50     }
51     return (dis[e]!=INF);
52 }
53 
54 void MCMF(int s,int e)
55 {
56     int Ans=0,Fee=0;
57     while (Spfa(s,e))
58     {
59         int d=INF;
60         for (int i=e; i!=s; i=edge[((pre[i]-1)^1)+1].to)
61             d=min(d,edge[pre[i]].Flow);
62         for (int i=e; i!=s; i=edge[((pre[i]-1)^1)+1].to)
63         {
64             edge[pre[i]].Flow-=d;
65             edge[((pre[i]-1)^1)+1].Flow+=d;
66         }
67         Ans+=d;
68         Fee+=d*dis[e];
69     }
70     printf("%d %d",Ans,Fee);
71 }
72 int main()
73 {
74     memset(&INF,0x7f,sizeof(INF));
75     scanf("%d%d%d%d",&n,&m,&s,&e);
76     for (int i=1; i<=m; ++i)
77     {
78         scanf("%d%d%d%d",&u,&v,&l,&c);
79         add(u,v,l,c);
80         add(v,u,0,-c);
81     }
82     MCMF(s,e);
83 }

網絡流之有上下界的最大流

(以後來填坑)

網絡流之多源點匯點的最大流

啊這個很簡單啊……

增設一個超級源點一個超級匯點

分別連接各個源點和各個匯點

同時連接的這些邊容量設置為INF

正確性……

一看就是對的好嗎(逃)

網絡流之頂點有容量限制的最大流

這個就有個很重要的思想了:

化邊為點

而這個思想不止在網絡流,在很多其他地方也有重要的應用

所以這裏我們就參照多源點和多匯點的最大流

將題目中的點化為邊就好了啊

Emmmmmmmmm……

網絡流之二分圖

二分圖的最大匹配

二分圖匹配是二分圖裏面最最基礎的。也有兩種求解方法。

一種是匈牙利算法,此處就不再贅述了。

另一種就可以建立網絡流模型,用Dinic求最大流。求出來最大流即為最大匹配。

模型的建立,就是給原本的二分圖增加一個源點和一個匯點,邊權全部為1,然後求最大流。

(如下圖)

有一種類型的匹配題目,由於做到過,也在這裏提一下。

要求三種物品匹配,給出對應關系,求最大匹配個數。

這裏就要用到拆點的思想了。具體參見luogu https://www.luogu.org/problemnew/show/1231

若要求一組最大匹配可行方案,則可以循環看邊的流量是否為0。

二分圖的覆蓋集

覆蓋集:對於每條邊,至少選一個端點,即至多不選一個端點。

1、 點權之和最小的覆蓋

建立網絡流模型:

源點--X點:容量為X點權 X點--Y點:容量為INF Y點到匯點:容量為Y點權

證明:什麽叫覆蓋集?對於每條邊,其兩端點至少有一個被選中。而割的定義為:任何一條源到匯的路徑不連通。而X-Y邊肯定不是屬於最小割(INF)。故任何一條X-Y邊一定和源點或匯點之一有且僅有一條連邊。畢竟反正路徑都不連通達到割的要求了,多割一條也沒什麽用。故二分圖的最小割是點權之和最小的覆蓋集。

2、 頂點數最多且點權之和盡量大的覆蓋

建立網絡流模型:

源點--X點:容量為1,費用為點權相反數

X點--Y點:容量為INF,費用為0

Y點到匯點:容量為1,費用為點權相反數

二分圖的獨立集

獨立集:對於每條邊,至多選一個端點,即至少不選一個端點。

最大匹配=最小覆蓋=總點數-最大獨立集

最大獨立集和最小覆蓋集是互補問題。

求最大獨立集,等價於求其補集,也就是說每條邊刨出至少一個端點,並且刨出的點的權和最小,這樣才使剩下的最多。|最大獨立集|+|最小覆蓋集|=所有點的權值之和。

網絡流之常見模型

1、 二分圖

上面有……

2、 最大權閉合子圖

所謂閉合圖,就是在一張圖中選出一個點的集合,保證這些點的出邊所連向的點也在該集合中。最大權閉合子圖指點權值最大的閉合子圖。

這一性質類似這道題目的要求:

s與所有正權值的點相連,所有負權值的點與t相連。這些邊的容量設為點的權值。若一個『實驗』需要某個『儀器』,就在它們之間連一條無窮大的邊。

最後的『收益』是用『總收益』減去『損失』(摘自gty學長課件)

裸題:luoguP3140

不明白就把樣例畫出來對著圖理解……

Update:突然覺得當成最小割理解可能好理解

3、 DAG的最小路徑覆蓋

其實這個也是用二分圖實現的。可以發現,每個點除了終點外,都有且僅有一條出邊。

如果無匹配,顯然要n條路徑才能覆蓋所有點,兩個點匹配意味著將可以把它們用一條路徑覆蓋,路徑數就可以減1(hzwer)

建立二分圖求最大匹配即可。

4、 最多/最大權不相交路徑

瞎拆點然後邊的容量亂搞限制一下就好了。

5、 最短路

6、 區間k覆蓋

http://blog.csdn.net/LOI_DQS/article/details/50847776

對於blog裏的二方法,每個區間連了一條費用為長度相反數的邊,所以跑這條路肯定是要比跑那些費用為0的路更優。不過由於源點和匯點的限制,如果覆蓋超過k,則強制取k。

7、 二分法

例如HNOI2007緊急疏散,看看題解就很好懂了。

當然有時候也用枚舉代替二分

學習筆記:網絡流