网络流回顾与总结
1、网络
网络,一种特殊的有向图,一个有向带权图 $G = (V,E)$,指定两个节点 $s$ 和 $t$($s \ne t$),分别称为源点和汇点,每条边的权值称为容量 $c$,特别地,若边 $\left\langle {u,v} \right\rangle \notin E$,则 $c\left( {u,v} \right) = 0$。
2、流
网络中的流是一个实值函数 $f:V \times V \to R$,满足下列性质:
(1)容量限制(Capacity Constraints):对于 $\forall u,v \in V$,要求 $f\left( {u,v} \right) \le c\left( {u,v} \right)$。通俗地讲,一条边的流量不能超过它的容量。
(2)反对称性(Skew Symmetry):对于 $\forall u,v \in V$,要求 $f\left( {u,v} \right) = -f\left( {u,v} \right)$。注意 $f(u,v)$ 是由 $u$ 到 $v$ 的净流,举例,如果由 $u$ 到 $v$ 有 $4$ 单位的实际流量以及由 $v$ 到 $u$ 有 $3$ 单位的实际流量,那么 $f(u,v) = 1$ 及 $f(v,u) = -1$。通俗地讲,一条边的净流量是有方向的,若方向与边的方向同向记为正,否则记为负。(根据本性质,不难得到 $f(u,u) = - f(u,u) \Rightarrow f(u,u) = 0$)
(3)流量守恒(Flow Conservation):对于 $\forall u \in V - \left\{ {s,t} \right\}$,要求 $\sum\limits_{v \in V} {f\left( {u,v} \right)} = 0$。通俗地讲,图上除源汇点外的任意一个节点,必须满足流入的流量等于流出的流量。
把流 $f$ 的值定义为 $\left| f \right| = \sum\limits_{v \in V} {f\left( {s,v} \right)}$,也就是说,从源点流出的全部流量之和(同样也等于流进汇点的全部流量之和),称为流的流值。
最大流就是所有流中流值最大的流。
设集合 $X,Y \subseteq V$,记 $f\left( {X,Y} \right) = \sum\limits_{u \in X} {\sum\limits_{v \in Y} {f\left( {u,v} \right)} }$
定理:设 $f$ 是网络 $G=(V,E)$ 中的一个流,那么
(1)对于 $\forall X \subseteq V$,有 $f\left( {X,X} \right) = 0$。
(2)对于 $\forall X,Y \subseteq V$,有 $f\left( {X,Y} \right) = - f\left( {Y,X} \right)$。
(3)对于 $\forall X,Y,Z \subseteq V$,其中 $X \cap Y = \emptyset$,有 $f\left( {X \cup Y,Z} \right) = f\left( {X,Z} \right) + f\left( {Y,Z} \right)$。
证明(2):
$\begin{array}{l} f\left( {X,Y} \right) \\ = \sum\limits_{u \in X} {\sum\limits_{v \in Y} {f\left( {u,v} \right)} } \\ = \left[ {f\left( {x_1 ,y_1 } \right) + \cdots + f\left( {x_1 ,y_n } \right)} \right] + \cdots + \left[ {f\left( {x_n ,y_1 } \right) + \cdots + f\left( {x_n ,y_n } \right)} \right] \\ = \left[ { - f\left( {y_1 ,x_1 } \right) - \cdots - f\left( {y_n ,x_1 } \right)} \right] + \cdots + \left[ { - f\left( {y_1 ,x_n } \right) - \cdots - f\left( {y_n ,x_n } \right)} \right] \\ = - \left[ {f\left( {y_1 ,x_1 } \right) + \cdots + f\left( {y_n ,x_1 } \right)} \right] - \cdots - \left[ {f\left( {y_1 ,x_n } \right) + \cdots + f\left( {y_n ,x_n } \right)} \right] \\ = - \left\{ {\left[ {f\left( {y_1 ,x_1 } \right) + \cdots + f\left( {y_n ,x_1 } \right)} \right] + \cdots + \left[ {f\left( {y_1 ,x_n } \right) + \cdots + f\left( {y_n ,x_n } \right)} \right]} \right\} \\ = - \sum\limits_{u \in Y} {\sum\limits_{v \in X} {f\left( {u,v} \right)} } \\ = - f\left( {Y,X} \right) \\ \end{array}$
证明(1):
由(2)可得 $f\left( {X,X} \right) = - f\left( {X,X} \right) \Rightarrow f\left( {X,X} \right) = 0$。
证明(3):
假设 $X = \left\{ {x_1 , \cdots ,x_n } \right\},Y = \left\{ {y_1 , \cdots ,y_n } \right\}$,由于 $X \cap Y = \emptyset$,
所以 $X \cup Y = \left\{ {x_1 , \cdots ,x_n ,y_1 , \cdots ,y_n } \right\}$,
因此不难得到
$\begin{array}{l} f\left( {X \cup Y,Z} \right) \\ = \left[ {f\left( {x_1 ,z_1 } \right) + \cdots + f\left( {x_1 ,z_n } \right)} \right] + \cdots + \left[ {f\left( {x_n ,z_1 } \right) + \cdots + f\left( {x_n ,z_n } \right)} \right] + \left[ {f\left( {y_1 ,z_1 } \right) + \cdots + f\left( {y_1 ,z_n } \right)} \right] + \cdots + \left[ {f\left( {y_n ,z_1 } \right) + \cdots + f\left( {y_n ,z = _n } \right)} \right] \\ = \sum\limits_{u \in X} {\sum\limits_{v \in Z} {f\left( {u,v} \right)} } + \sum\limits_{u \in Y} {\sum\limits_{v \in Z} {f\left( {u,v} \right)} } \\ = f\left( {X,Z} \right) + f\left( {Y,Z} \right) \\ \end{array}$
不难发现,这三个定理都是比较基础的性质推导,而它们都是有比较具象的意义的,那就是网络中的若干个节点,可以缩成一个节点,不影响网络的性质。
3、残留网络与增广路
对于网络 $G=(V,E)$,设流 $f$ 是 $G$ 中的流,对于 $G$ 中的每条边 $\left\langle {u,v} \right\rangle$,定义残留容量为在不超过容量限制的条件下,可以通过的额外的流量
$c_f \left( {u,v} \right) = c\left( {u,v} \right) - f\left( {u,v} \right)$
残留网络的定义:对于网络 $G=(V,E)$ 与流 $f$,残留网络为 $G_f = (V,E_f)$,其中边集 $E_f = \left\{ {\left( {u,v} \right) \in V \times V|c_f \left( {u,v} \right) > 0} \right\}$。
事实上,残留网络也是一个网络,原网络的一个流和对应的残留网络中的流相加依然是原网络中的一个流。
增广路 $p$ 为残留网络 $G_f$ 上的源点 $s$ 到汇点 $t$ 的一条简单路径,该路径的残留容量为可以沿该路径增加的最多额外流量 $c_f = \min \left\{ {c_f \left( {u,v} \right)|\left\langle {u,v} \right\rangle \in p} \right\}$,
由于我们知道残留网络上的所有容量必须是大于零的,所以增广路的残留容量也是严格大于零的。
增广路的可增广性质:定义流 $f_p$ 为
$f_p = \left\{ {\begin{array}{*{20}c} {c_f \left( p \right),\left\langle {u,v} \right\rangle \in p} \hfill \\ { - c_f \left( p \right),\left\langle {u,v} \right\rangle \in p} \hfill \\ {0,} \hfill \\ \end{array}} \right.$
则给定一个网络 $G$ 的一个流 $f$,则 $f+f_p$ 依然是网络 $G$ 的一个流。
4、最小割最大流定理
网络 $G=(V,E)$ 中,一个割 $[S,T]$ 将点集 $V$ 划分成两个部分,使得 $s \in S,t \in T$。符号 $[S,T]$ 代表边集 $\left\{ {\left\langle {u,v} \right\rangle |\left\langle {u,v} \right\rangle \in E,u \in S,v \in T} \right\}$。
穿过割 $[S,T]$ 的净流定义为 $f\left( {S,T} \right)$,割 $[S,T]$ 的容量定义为 $c\left( {S,T} \right)$,一个网络的最小割也就是该网络中的容量最小的割。
最小割最大流定理:设流 $f$ 为网络 $G=(V,E)$ 中的一个流,则
$f$ 是 $G$ 的一个最大流 $\Leftrightarrow$ $\left| f \right| = f\left[ {S,T} \right]$
且 $\left| f \right| \le c\left[ {S,T} \right]$。
5、Edmonds-Karp算法
EK算法的思想很简单,在残留网络上用BFS寻找一条增广路,把它增广了,然后再来一次BFS寻找下一条增广路,直到找不到为止。
算法模板可参见:https://www.cnblogs.com/dilthey/p/7358407.html#EK
时间复杂度 $O(n m^2)$,实际应用中远远达不到该上界,一般可以处理 $10^3$ 到 $10^4$ 规模的网络。
6、Dinic算法
时间复杂度 $O(n^2 m)$,而且实际应用中远远达不到该上界,一般可以处理 $10^4$ 到 $10^5$ 规模的网络。
Dinic算法求二分图最大匹配的时间复杂度为 $O(m \sqrt n)$,实际表现则更快。
算法步骤:
1、在残留网络上BFS求出每个节点的深度,构造分层图。
2、在分层图上DFS寻找增广路,在回溯时实时更新剩余容量。另外,每个点可以流向多条出边(相当于寻找了“增广树”),同时加入若干剪枝。
算法模板(POJ1273):
#include<cstdio> #include<cstring> #include<vector> #include<queue> using namespace std; const int INF=0x3f3f3f3f; const int maxn=5e4+10; const int maxm=3e5+10; struct Edge{ int u,v,c,f; }; struct Dinic { int s,t; //源点汇点 vector<Edge> E; vector<int> G[maxn]; void init(int l,int r) { E.clear(); for(int i=l;i<=r;i++) G[i].clear(); } void addedge(int from,int to,int cap) { E.push_back((Edge){from,to,cap,0}); E.push_back((Edge){to,from,0,0}); G[from].push_back(E.size()-2); G[to].push_back(E.size()-1); } int dist[maxn],vis[maxn]; queue<int> q; bool bfs() //在残量网络上构造分层图 { memset(vis,0,sizeof(vis)); while(!q.empty()) q.pop(); q.push(s); dist[s]=0; vis[s]=1; while(!q.empty()) { int now=q.front(); q.pop(); for(int i=0;i<G[now].size();i++) { Edge& e=E[G[now][i]]; int nxt=e.v; if(!vis[nxt] && e.c>e.f) { dist[nxt]=dist[now]+1; q.push(nxt); vis[nxt]=1; } } } return vis[t]; } int dfs(int now,int flow) { if(now==t || flow==0) return flow; int rest=flow,k; for(int i=0;rest>0 && i<G[now].size();i++) { Edge &e=E[G[now][i]]; int nxt=e.v; if(e.c>e.f && dist[nxt]==dist[now]+1) { k=dfs(nxt,min(rest,e.c-e.f)); if(!k) dist[nxt]=0; //剪枝,去掉增广完毕的点 e.f+=k; E[G[now][i]^1].f-=k; rest-=k; } } return flow-rest; } int mf; //存储最大流 int maxflow() { mf=0; int flow=0; while(bfs()) while(flow=dfs(s,INF)) mf+=flow; return mf; } }dinic; int n,m; int main() { while(scanf("%d%d",&m,&n)!=EOF) { dinic.init(1,n); for(int from,to,cap,i=1;i<=m;i++) { scanf("%d%d%d",&from,&to,&cap); dinic.addedge(from,to,cap); } dinic.s=1,dinic.t=n; dinic.maxflow(); printf("%d\n",dinic.mf); } }
7、最小费用最大流的Edmonds-Karp算法
上面已经描述过了最基础的EK算法,而最小费用最大流,就是给定每条边上单位流量所需花费,在保证最大流的前提下,费用越小越好。
那么不难想到,在普通的EK算法中,我们每次都是无目的地用BFS在残留网络中选择了某一条增广路(或者说就是寻找到了一条从 $s$ 到 $t$ 的路径)进行增广,
而一旦我们改用spfa在残留网络中寻找从 $s$ 到 $t$ 的一条边权为费用的最短路,那么我们现在要增广的路径就正好是费用最小的路径了,
那么,剩下来的事情不变,一直寻找增广路进行增广直到没有增广路,就求得了最小费用最大流。
算法模板(Luogu3381):
#include<bits/stdc++.h> using namespace std; const int maxn=5000+10; const int INF=0x3f3f3f3f; struct Edge{ int u,v,cap,flow,cost; }; struct MCMF { int s,t; //源点汇点 vector<Edge> E; vector<int> G[maxn]; void init(int l,int r) { E.clear(); for(int i=l;i<=r;i++) G[i].clear(); } void addedge(int from,int to,int cap,int cost) { E.push_back((Edge){from,to,cap,0,cost}); E.push_back((Edge){to,from,0,0,-cost}); G[from].push_back(E.size()-2); G[to].push_back(E.size()-1); } int d[maxn],vis[maxn]; int aug[maxn],pre[maxn]; bool spfa(int s,int t,int &flow,int &cost) { memset(d,INF,sizeof(d)); memset(vis,0,sizeof(vis)); queue<int> q; q.push(s); d[s]=0, vis[s]=1, pre[s]=0, aug[s]=INF; while(!q.empty()) { int now=q.front(); q.pop(); vis[now]=0; for(int i=0;i<G[now].size();i++) { Edge& e=E[G[now][i]]; int nxt=e.v; if(e.cap>e.flow && d[nxt]>d[now]+e.cost) { d[nxt]=d[now]+e.cost; pre[nxt]=G[now][i]; aug[nxt]=min(aug[now],e.cap-e.flow); if(!vis[nxt]) { q.push(nxt); vis[nxt]=1; } } } } if(d[t]==INF) return 0; flow+=aug[t]; cost+=d[t]*aug[t]; for(int i=t;i!=s;i=E[pre[i]].u) { E[pre[i]].flow+=aug[t]; E[pre[i]^1].flow-=aug[t]; } return 1; } int mc,mf; void solve() { int flow=0,cost=0; while(spfa(s,t,flow,cost)); mc=cost; mf=flow; } }mcmf; int main() { int N,M,S,T; scanf("%d%d%d%d",&N,&M,&S,&T); mcmf.init(1,N); mcmf.s=S, mcmf.t=T; for(int i=1;i<=M;i++) { int u,v,c,a; scanf("%d%d%d%d",&u,&v,&c,&a); mcmf.addedge(u,v,c,a); } mcmf.solve(); printf("%d %d\n",mcmf.mf,mcmf.mc); }
由于SPFA的复杂度波动很大的缘故……记录一下,在本题 $n \le 5000,m \le 50000$ 情况下均在1000ms内通过了所有测试数据。