网络流基础
感谢某个同学问我网络流问题的同学,让我在有生之年整理了一把网络流。
大部分资料来源于:洛谷暑期营的课件
一,一些关于网络流的概念
网络流图:一张有向连通图,有且只有一个点的入度为零,这个点称为源点,有且只有一个点的出度为零
这个点称为汇点,每个边有一个非负权值c称为这条边的容量。
容许流:在网络流图中,对于每条边 e = (i, j) 给定实数 fe ,如果满足 fe ≤ ce ; 对于任意 x ̸= S,T ,
∑ e=(x,i) fe = ∑ e=(i,x) fe ;(流量平衡) ∑ e=(S,i) fe = ∑ e=(i,T) fe = W , 则这一组 f 称为
该网络的一个容许流,W 称为它的流量。
最大流:求一个容许流使得它的 W 最大,用通俗易懂的话说,最大流就是给源点赋一个数,你要让这个值能够通过
这张网络流图的路径流到汇点,你想让这个值尽量的大。
二,最大流算法
一类解决网络流求最大流问题的基本思想——Ford-Fulkerson 标号算法
该算法的核心思想为不断寻找增广路。
何为增广(源)路,就是找到一条从源点到汇点的路径,使得这条路径的流量不为零,就找到了一条增源路。
算法过程:
记录 ve = ce − fe ,为一条边的剩余可用容量。 对于每一条边 e = (i, j) ,增加一条 反向弧 e ′ = (j, i) ,ve’=0。
首先给 S 标号 (−, inf) 。 不断检查,如果存在一个点 i 已被标号为 (di, δi) ,对于 e = (i, j) 有 ve > 0 且 j 未被标号,
则给 j 标号 (e, min(δi, ve)) 。(可以通过 DFS 或 BFS 实现) 如果某个时刻 T 被标号,则找到了一条流量为 w = δT 的增广路,
将沿着 di 一路回到 S 的所有边 e 的 ve 减去 w ,同时将所有 v ′ e 增加 w 。 如果 T 无法被标号,则说明当前容许流就是最大流。
建反向边的意义:让我们有“后悔”的机会
比如
显然,这张图的最大流为2,但我们找到的这条路为1,所以我们就让流量从3到2再流回去,这样就实现了“后悔”。
显然这个算法的效率比较差(是很差),它的效率和边的容量有直接关系,比如上图将1-2,2-4,1-3,3-4改为1e9后
这个算法最差执行2e9才会结束。
所以就出现了EdmondsKarp算法。
EdmondsKarp:每次沿着一条最短(边数)的增光路去增广,这样上图执行两次就可以结束,只需要在FF的基础上用BFS打标记即可。
至于为什么是BFS(最短路里如果边权为1,可以用BFS求最短路大家都会吧),就这样。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<queue> 6 #define maxn 10010 7 #define INF 2147483647 8 9 using namespace std; 10 11 struct node 12 { 13 int ed,nxt,len; 14 }; 15 node edge[maxn*20]; 16 int n,m,cnt=-1,first[maxn],S,T,ans; 17 int pre_u[maxn],pre_e[maxn],flow[maxn]; 18 bool vis[maxn]; 19 20 inline void add_edge(int s,int e,int d) 21 { 22 ++cnt; 23 edge[cnt].ed=e; 24 edge[cnt].len=d; 25 edge[cnt].nxt=first[s]; 26 first[s]=cnt; 27 return; 28 } 29 30 inline bool bfs(int s,int t) 31 { 32 queue <int> q; 33 memset(vis,false,sizeof(vis)); 34 memset(pre_u,-1,sizeof(pre_u)); 35 memset(pre_e,-1,sizeof(pre_e)); 36 memset(flow,0,sizeof(flow)); 37 pre_u[s]=s; vis[s]=true; flow[s]=INF; 38 q.push(s); 39 while(!q.empty()) 40 { 41 int p=q.front(); q.pop(); 42 for(register int i=first[p];i!=-1;i=edge[i].nxt) 43 { 44 int e=edge[i].ed; 45 if(!vis[e]&&edge[i].len!=0) 46 { 47 pre_u[e]=p; 48 pre_e[e]=i; 49 vis[e]=true; 50 flow[e]=min(flow[p],edge[i].len); 51 if(e==t) return true; 52 q.push(e); 53 } 54 } 55 } 56 return false; 57 } 58 59 inline void EK() 60 { 61 while(bfs(S,T)) 62 { 63 for(register int i=T;i!=S;i=pre_u[i]) 64 { 65 edge[pre_e[i]].len-=flow[T]; 66 edge[pre_e[i]^1].len+=flow[T]; 67 } 68 ans+=flow[T]; 69 } 70 return; 71 } 72 73 int main() 74 { 75 memset(first,-1,sizeof(first)); 76 scanf("%d%d%d%d",&n,&m,&S,&T); 77 for(register int i=1;i<=m;++i) 78 { 79 int s,e,d; 80 scanf("%d%d%d",&s,&e,&d); 81 add_edge(s,e,d); 82 add_edge(e,s,0); 83 } 84 EK(); 85 printf("%d\n",ans); 86 return 0; 87 }
3,举世闻名的Dinic
让我们继续考虑优化EK,我们发现EK每次只会找到一条增广路,造成了时间的浪费,那么我们为什么不能一次
找多条增广路呢??当然可以。
思路:bfs分层,dfs寻找路。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<queue> 6 #define INF 2147483647 7 #define maxn 10010 8 9 using namespace std; 10 11 struct node 12 { 13 int ed,len,nxt; 14 }; 15 node edge[maxn*20]; 16 int n,m,first[maxn],cnt=-1,dis[maxn]; 17 int S,T,ans; 18 bool vis[maxn]; 19 20 inline void add_edge(int s,int e,int d) 21 { 22 ++cnt; 23 edge[cnt].ed=e; 24 edge[cnt].len=d; 25 edge[cnt].nxt=first[s]; 26 first[s]=cnt; 27 return; 28 } 29 30 inline bool bfs(int s,int t) 31 { 32 memset(dis,-1,sizeof(dis)); 33 memset(vis,false,sizeof(vis)); 34 queue <int> q; 35 dis[s]=0; 36 q.push(s); 37 while(!q.empty()) 38 { 39 int p=q.front(); q.pop(); 40 for(register int i=first[p];i!=-1;i=edge[i].nxt) 41 { 42 int e=edge[i].ed; 43 if((dis[e]==-1)&&edge[i].len!=0) 44 { 45 dis[e]=dis[p]+1; 46 q.push(e); 47 } 48 } 49 } 50 if(dis[t]==-1) return false; 51 else return true; 52 } 53 54 inline int dfs(int s,int max_flow) 55 { 56 if(s==T) return max_flow; 57 int now_flow=0; 58 for(register int i=first[s];i!=-1;i=edge[i].nxt) 59 { 60 int e=edge[i].ed; 61 if(dis[e]==dis[s]+1&&edge[i].len!=0) 62 { 63 int flow=dfs(e,min(edge[i].len,max_flow)); 64 if(!flow) continue; 65 edge[i].len-=flow; 66 edge[i^1].len+=flow; 67 max_flow-=flow; 68 now_flow+=flow; 69 if(!max_flow) break; 70 } 71 } 72 return now_flow; 73 } 74 75 inline void dinic() 76 { 77 while(bfs(S,T)) ans+=dfs(S,INF); 78 return; 79 } 80 81 int main() 82 { 83 scanf("%d%d%d%d",&n,&m,&S,&T); 84 memset(first,-1,sizeof(first)); 85 for(register int i=1;i<=m;++i) 86 { 87 int s,e,d; 88 scanf("%d%d%d",&s,&e,&d); 89 add_edge(s,e,d); 90 add_edge(e,s,0); 91 } 92 dinic(); 93 printf("%d\n",ans); 94 return 0; 95 }
三,最小割定理
最小割:去除一些边,使得这些边的边权和尽量小的情况下,使得从源点到汇点不连通。
定理:最小割等于最大流