网络流问题在实际解决题目中有很多用处
有时可与二分图匹配相搭配(然而我还没有学二分图匹配2333)
所以我还是决定学一下网络流
于是便有了这篇博客
废话不多说
先介绍下概念
关于网络流的基本概念
图,边,点
设边(Edge)的集合为E
点(vertex)的集合为V
图为G
则G=(V,E)
\(\color{pink}{S指源点(起点),T指汇点(终点)}\)
\(\color{pink}{注意,这里E和V是集合,而G是一个二元组}\)
\(\color{pink}{二元组:数据结构的一种(不是指信息的,也许信息也有 雾),二元组(D,R),D为数据元素的有限集,R为D的关系之间的有限集}\)
\(\color{pink}{明显这里D指V,R指E}\)
容量网络
G(V,E)只不过要求
有向
相邻两点间的最大边大于0(c(u,v)>0)
则称G为容量网络
弧的流量
话说 我不懂为什么都叫它弧不叫边的2333
即实际流量用f(u,v)表示
网络流
即集合 f = { u\(\in\)V,v\(\in\)V-{u}|f(u, v) }
可行流
0 ≤ f(u,v) ≤ c(u,v)
平衡条件除源点S和汇点T外流入流量总和=流出流量总和
增广路
即一条从S到T的路径且路径上每条边残留容量都为正(c(u,v)-f(u,v)>0)
增广
将S到T的一条增广路拿出来,设其中的最小边权(残留容量)为minl
将该路途上的每边 -= minl
将minl累加至ans中
以上 为前置概念
算法
题目
接下来的题目以P3376 【模板】网络最大流为例子
EdmondKarp
该算法时间复杂度O(V\(E^2\))
思路不断找增广路进行增广,当找不到增广路时,即此时的ans就为网络最大流
找增广路
inline bool Bfs() { bool vis[MAXN] = {}; memset(pre,0,sizeof(pre)); queue<int> q; q.push(s); vis[s] = 1; while(!q.empty()) { int u = q.front(); if(u == t) return 1; q.pop(); for(int i = fire[u];i;i = Edge[i].next) { int v = Edge[i].v; if(!vis[v]&&Edge[i].w) { vis[v] = 1; pre[v].v = u; pre[v].Edge = i; //pre用来记录路径 if(v == t) return 1; q.push(v); } } } return 0; }
EdmondKarp
inline int EdmondKarp() { int ans = 0; while(Bfs()) { int i,minl = inf; for(i = t;i != s;i = pre[i].v) minl = min(minl,Edge[pre[i].Edge].w); //找最小值 for(i = t;i != s;i = pre[i].v) { Edge[pre[i].Edge].w -= minl; //Edge[pre[i].Edge ^ 1].w += minl; } //将该路途上的每边 -= minl ans += minl; //将minl累加至ans中 } return ans; }
然后这就是大概思路
\(\color{pink}{但是}\)
这样会错

假设上面每条边容量为1
如果找到的第一条增广路为1-2-3-4
那么

再也找不到增广路
此时ans 为 1
但显然ans为2
\(\color{pink}{WA}\)
原因很显然这样随便走很明显不对啊 又没什么定理保证正确
不过 EdmondKarp还有一点
我们要给程序一个\(\color{pink}{反悔}\)的机会
口技·习传
不玩梗了
建双向边, 将该路途上的每边 -= minl的时候同时将该路途上的每边的反边 += minl
即加上上面被我注释掉的代码

建双向边后

蓝边权值1,红边权值0,绿边权值1
第二次就可以找到1-3-2-4了
我们用的是链式前向星
为方便我们让一条边与她的反边相邻
即 使 边id^1=反边id且反边id^1=边id 即 min(边id,反边id) + 1 = max(边id,反边id)
所以第一条边id必须为偶数
否则如果第一条边从奇数开始 她的
反边id=边id+1!=边id^1
代码
#include <queue> #include <cstdio> #include <climits> #include <cstring> #include <iostream> using namespace std; const int MAXM = 200010,MAXN = 10010,inf = INT_MAX; struct Node { int v,w,next; Node(){v = w = next=0;} }Edge[MAXM]; struct Node2 { int v,Edge; }pre[MAXN]; int cnt,fire[MAXN],s,t; inline void AddEdge(int u,int v,int w) { Edge[++cnt].v = v; Edge[cnt].w = w; Edge[cnt].next = fire[u]; fire[u] = cnt; } inline void Build(int u,int v,int w) { AddEdge(u,v,w); AddEdge(v,u,0); //反边权值为0 } inline bool Bfs() { bool vis[MAXN] = {}; memset(pre,0,sizeof(pre)); queue<int> q; q.push(s); vis[s] = 1; while(!q.empty()) { int u = q.front(); if(u == t) return 1; q.pop(); for(int i = fire[u];i;i = Edge[i].next) { int v = Edge[i].v; if(!vis[v]&&Edge[i].w) { vis[v] = 1; pre[v].v = u; pre[v].Edge = i; if(v == t) return 1; q.push(v); } } } return 0; } inline int EdmondKarp() { int ans = 0; while(Bfs()) { int i,minl = inf; for(i = t;i != s;i = pre[i].v) minl = min(minl,Edge[pre[i].Edge].w); for(i = t;i != s;i = pre[i].v) { Edge[pre[i].Edge].w -= minl; Edge[pre[i].Edge ^ 1].w += minl; } ans += minl; } return ans; } int main() { cnt = -1; 这里设为-1,但Build里+了1 所以实际上第一条边从零(偶数)开始的(~~魔法书~~/~~异世界生活~~) int i,n,m; scanf("%d%d%d%d",&n,&m,&s,&t); for(i = 1;i <= m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); Build(u,v,w); } int ans = EdmondKarp(); printf("%d",ans); return 0; }
这样就可以AC这道题P3376 【模板】网络最大流
\(\color{pink}{但是}\)
这样的代码还有bug 所以我说洛谷数据水啊
我的链式前向星是这样写的 明显条件是当i等于0时退出 但 之前cnt初值为-1 第一条边id为0 所以当i等于0的时候也可能指的第一条边 订正 将cnt改为 1 for(int i = fire[u];i;i = Edge[i].next) { int v = Edge[i].v; if(!vis[v]&&Edge[i].w) { vis[v] = 1; pre[v].v = u; pre[v].Edge = i; if(v == t) return 1; q.push(v); } }
#include <queue> #include <cstdio> #include <climits> #include <cstring> #include <iostream> using namespace std; const int MAXM = 200010,MAXN = 10010,inf = INT_MAX; struct Node { int v,w,next; Node(){v = w = next=0;} }Edge[MAXM]; struct Node2 { int v,Edge; }pre[MAXN]; int cnt,fire[MAXN],s,t; inline void AddEdge(int u,int v,int w) { Edge[++cnt].v = v; Edge[cnt].w = w; Edge[cnt].next = fire[u]; fire[u] = cnt; } inline void Build(int u,int v,int w) { AddEdge(u,v,w); AddEdge(v,u,0); } inline bool Bfs() { bool vis[MAXN] = {}; memset(pre,0,sizeof(pre)); queue<int> q; q.push(s); vis[s] = 1; while(!q.empty()) { int u = q.front(); if(u == t) return 1; q.pop(); for(int i = fire[u];i;i = Edge[i].next) { int v = Edge[i].v; if(!vis[v]&&Edge[i].w) { vis[v] = 1; pre[v].v = u; pre[v].Edge = i; if(v == t) return 1; q.push(v); } } } return 0; } inline int EdmondKarp() { int ans = 0; while(Bfs()) { int i,minl = inf; for(i = t;i != s;i = pre[i].v) minl = min(minl,Edge[pre[i].Edge].w); for(i = t;i != s;i = pre[i].v) { Edge[pre[i].Edge].w -= minl; Edge[pre[i].Edge ^ 1].w += minl; } ans += minl; } return ans; } int main() { cnt = 1; int i,n,m; scanf("%d%d%d%d",&n,&m,&s,&t); for(i = 1;i <= m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); Build(u,v,w); } int ans = EdmondKarp(); printf("%d",ans); return 0; }
这样才没问题T_T
Dinic(Dinitz)
作者叫Dinitz
后来某大佬(雾)教课时念成了Dinic
于是大家都叫这个算法为Dinic算法了2333
这个算法大概就是EdmondKarp的升级版
理论上快很多 但最慢会退化为\(O(VE^2)\)
思路就是 考虑到EdmondKarp每次都重新一遍Bfs找增广路
我们能否在一次或几次搜索中找到很多条增广路呢?
这就是Dinic算法(连我也这样叫 恩 作者的真名不用管了)的核心处理之处了
具体实现
引入一个层次网络的概念
其实就是给予\(\forall\) v \(\in\)V 一个值 \(depth_i\)
记录从源点到她最少需要经过几条还活着的边(边权为0的已经死了)
这个操作一遍Bfs()搞定
然后找增广路就用Dfs()来找
\(Bfs\)
inline bool Bfs() { memset(depth,-1,sizeof(depth)); queue<int> q; q.push(s); depth[s] = 1; while(!q.empty()) { int u = q.front(); q.pop(); for(int i = fire[u];i;i = Edge[i].next) { int v = Edge[i].v; if(depth[v] == -1&&Edge[i].w) { depth[v] = depth[u] + 1; q.push(v); } } } return (depth[t] != -1); }
Dfs
inline int Dfs(int u,int minl) { if(u == t) return minl; int i,delta1 = 0; for(i = head[u];i;i = Edge[i].next) { int v = Edge[i].v,w = Edge[i].w; head[u] = i; 不断更新head[u] 直接用fire[u]也没错 但在同一次Dfs已经走过的最后的一条边失去价值 减少时间复杂度 if(depth[v] == depth[u] + 1&&w) { int delta = Dfs(v,min(minl,w)); minl -= delta; delta1 += delta; Edge[i].w -= delta; Edge[i ^ 1].w += delta; if(!minl) break; } } if(!delta1) depth[u] = -1; return delta1; }
Dinic(Dinitz)
inline int Dinic() { int ans = 0; while(Bfs()) { for(int i = 1;i <= n;i++) head[i] = fire[i]; 因为head在Dfs中进行了更改,但这对另外一次Dfs不造成影响 因为另外一次Dfs前由再进行了一次Bfs改变了depth 所以这里得把值赋回去 ans += Dfs(s,INT_MAX); } return ans; }
完整代码
#include <queue> #include <cstdio> #include <climits> #include <cstring> #include <iostream> using namespace std; const int MAXM = 200010,MAXN = 10010,inf = INT_MAX; struct Node { int v,w,next; Node(){v = w = next=0;} }Edge[MAXM]; int cnt,fire[MAXN],head[MAXN],s,t,depth[MAXN],n; inline void AddEdge(int u,int v,int w) { Edge[++cnt].v = v; Edge[cnt].w = w; Edge[cnt].next = fire[u]; fire[u] = cnt; } inline void Build(int u,int v,int w) { AddEdge(u,v,w); AddEdge(v,u,0); } inline bool Bfs() { memset(depth,-1,sizeof(depth)); queue<int> q; q.push(s); depth[s] = 1; while(!q.empty()) { int u = q.front(); q.pop(); //printf("%d:",u); for(int i = fire[u];i;i = Edge[i].next) { int v = Edge[i].v; //printf("[%d %d %d] ",u,i,v); if(depth[v] == -1&&Edge[i].w) { depth[v] = depth[u] + 1; q.push(v); } } //putchar('\n'); } return (depth[t] != -1); } inline int Dfs(int u,int minl) { if(u == t) return minl; int i,delta1 = 0; for(i = head[u];i;i = Edge[i].next) { int v = Edge[i].v,w = Edge[i].w; head[u] = i; if(depth[v] == depth[u] + 1&&w) { int delta = Dfs(v,min(minl,w)); minl -= delta; delta1 += delta; Edge[i].w -= delta; Edge[i ^ 1].w += delta; if(!minl) break; } } if(!delta1) depth[u] = -1; return delta1; } inline int Dinic() { int ans = 0; while(Bfs()) { for(int i = 1;i <= n;i++) head[i] = fire[i]; ans += Dfs(s,INT_MAX); } return ans; } int main() { cnt = 1; int i,m; scanf("%d%d%d%d",&n,&m,&s,&t); for(i = 1;i <= m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); Build(u,v,w); } int ans = Dinic(); printf("%d",ans); return 0; }