网络流小记
I.基本定义:
-
网络:一张有向图。
-
流量:经过一条边的流的大小,一条边 \((u,v)\) 的流量记为 \(flow(u,v)\), 一个网络的流量定义为 \(∑f(s,x)\)。
-
容量:一条边的流量上限,一条边 \((u,v)\) 的容量记为 \(cap(u,v)\)。
-
费用:经过一条边单位流量的所需费用,一条边 \((u,v)\) 的费用记为 \(cost(u,v)\)。
-
源点:所有流的起始点, 记为 \(s\)。
-
汇点:所有流的终止点,记为 \(t\)。
-
割:是网络的一个划分, 一个割 {\(S, T\)} 的容量定义为 \(||S, T|| = \sum_{u \in S} \sum_{v \in T} cap(u, v)\)。
-
残量网络:每条边剩余可走的流量组成的网络。
网络流的性质:
-
斜对称性:\(flow(u,v) = -flow(v,u)\)
-
流量守恒:\(\sum_{u \ne x, t\ne y} flow(u, x) = \sum flow(y,u)\)
-
容量限制: \(flow(u,v) \le cap(u,v)\)
II.一些定理:
增广路定理:
- 增广路:在残量网络中的一条 \(s\) 到 \(t\) 的路径,满足路径上的残量均大于 \(0\)。
一个流为最大流当且仅当网络中没有增广路,证明显然。
最大流最小割定理:
对于任意网络,最大流 \(f\) 和最小割 \(\{S, T\}\) 总是满足 \(|f| = ||S, T||\)。
- 证明:首先显然有 \(|f| \le ||S, T||\),因为根据割的定义 \(S, T\) 互不相交,\(S -> T\) 的流量一定大于等于 \(|f|\),考虑如何构造取到等号。
III.最大流:
EK 算法:
对于求一张图的最大流,我们考虑引入反向边的概念。
- 反向边:一条边的反向边定义为一条流向与原边相反,容量为 \(0\),流量与原边相反的边。
经过反向边相当于做退流操作,类似于反悔,这也是 EK 算法的关键之处。具体的,我们将反向边一并加入到残量网络中,并与原边一起考虑。
由上面的定理,我们可以得到以下算法:
-
在残量网络上找一条增广路,将这条路径上的流大小记为 \(flow\)。
-
将路径上的所有边加上 \(flow\),反向边减掉 \(flow\)。
我们暴力的使用 \(dfs\) 去找,时间复杂度 \(O(nm^2)\)。
Dinic 算法:
考虑优化上面的算法,\(\rm EK\) 算法一次只找到一条增广路,这使得有一些无用的操作,考虑一次进行多路增广,即一次找到多条增广路。
我们考虑对残量网络进行分层,这样我们可以一次找到多条长度相同的增广路并进行增广。分层容易用 \(\rm BFS\) 实现。
接着我们考虑进行多路增广,这个使用 \(\rm DFS\) 也不难实现。这里有一个叫做当前弧优化的东西,作用是让已经考虑过的边不再考虑,可以保证时间复杂度在 \(O(n^2m)\),实现开一个数组记录即可。建议结合代码食用。
qwq
一些例题:
(有待修缮)
IV.费用流:
在费用流问题中,每条边多了一个元素称作费用,一个流的费用等于该流经过的边的费用和乘上流的大小。费用流问题中最经典的是最小费用最大流问题,即在最大化流大小的情况下最小化费用和。
其实这个问题非常简单,我们每次选择流最大的增广路中最小费用的进行增广即可。
qwq
namespace Dinic{
struct edge{
int v, flow, cap, cost, next;
}edges[M << 1];
int head[N], idx = 1;
int cur[N], dis[N], s, t, siz, maxflow, mincost;
bool vis[N];
void add_edge(int u, int v, int cap, int cost){
edges[++idx] = {v, 0, cap, cost, head[u]};
head[u] = idx;
}
void addline(int u, int v, int cap, int cost){add_edge(u, v, cap, cost); add_edge(v, u, 0, -cost);}
bool SPFA(){
for(int i = 1; i <= siz; i++) dis[i] = INF, cur[i] = head[i], vis[i] = false;
dis[s] = 0; queue<int> Q; Q.push(s);
while(!Q.empty()){
int u = Q.front(); Q.pop();
vis[u] = false;
for(int i = head[u]; i; i = edges[i].next){
int v = edges[i].v;
if(dis[v] > dis[u] + edges[i].cost && edges[i].cap > edges[i].flow){
dis[v] = dis[u] + edges[i].cost;
if(!vis[v]) Q.push(v), vis[v] = true;
}
}
}
for(int i = 1; i <= siz; i++) vis[i] = false;
return (dis[t] != INF);
}
int dfs(int u, int flow, int &cost){
if((!flow) || u == t) return flow;
int ret = 0; vis[u] = true;
for(int& i = cur[u]; i; i = edges[i].next){
int v = edges[i].v, d;
if((!vis[v]) && dis[v] == dis[u] + edges[i].cost && (d = dfs(v, min(flow - ret, edges[i].cap - edges[i].flow), cost))){
ret += d; edges[i].flow += d; edges[i ^ 1].flow -= d; cost += edges[i].cost * d;
if(flow == ret) return flow;
}
}
vis[u] = false;
return ret;
}
void dinic(){
while(SPFA()){
int cost = 0;
maxflow += dfs(s, INF, cost); mincost += cost;
}
}
}
V.上下界网络流:
现在我们在普通网络流的基础上再给每条边增加一个属性:流量上界,即经过一条边的流量至少要大于等于流量下界。
1.无源汇上下界可行流:
对于一张