學習筆記:網絡流
網絡流
網絡流是省選必備算法啊……早就想學了但一直沒空……現在學習一下順便做做筆記
(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緊急疏散,看看題解就很好懂了。
當然有時候也用枚舉代替二分
學習筆記:網絡流