网络流
Posted akakw1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流相关的知识,希望对你有一定的参考价值。
topic
持续更新
剩余内容:
- ISAP
- HLPP
- 费用流
- 上下界网络流
网络流
基本概念
定义
给定一个有向图,其中有一个源点(起点)和一个汇点(终点),每条边有一个容量 (c_i) 和一个流量 (f_i) 表示最大可以通过这条边的流量和这条边的流量,如果满足 (f_i leq c_i) 且每个点出边的流量和等于这个点入边的流量和(其中源点出边的流量等于汇点入边的流量),就称这是一个网络流
简单理解:有一些节点和一些单向水管,水管连接两个节点且每个单位时间内只能通过一定的水,我们从一个节点持续灌水,从一个节点接水,达到恒定状态后的水流就是一个网络流
形象描述:在不超过流量限制的情况下,“流”从源点不断产生,流经网络图,最终全部归于汇点
最大流
顾名思义,一个网络流使得从源点到汇点的总流量最大
最小割
删去一些边使得源点和汇点不连通且删去边的容量和最小
增广路
若一条从源点到汇点的路径上各边的剩余流量都大于 0,则称这条路径是一条增广路
残量网络
网络中所有节点及剩余流量大于 (0) 的边构成的子图被称为残量网络
最大流
Edmonds-Karp 增广路算法
每次从源点开始 (BFS) 找到增广路,然后进行让一个流从源点经过增广路到达汇点,并更新剩余流量,直到无法找到增广路为止
具体方法
每次 (BFS) 时只考虑剩余流量大于 (0) 的边,并记录下剩余流量的最小值,然后再把这个最小值加入答案,并把增广路上的每条边的剩余流量减去这个最小值
但是我们发现,这样找出来的是一个可行流而不一定是最大流,原因是我们可能使用了一个不优的流,所以我们还要构建这个图的反图(即一个图,图中的每条边的剩余流量为原图中已经使用的流量),这样我们就可以支持撤回操作了,即每次找增广路的时候同时考虑反图(直接把反图当做原图处理),如果一个流经过了反图中的边,则这个流中有撤回操作
实现方法
关于反图的构建,我们可以采用邻接表成对存储的方法,即对于边 (i),编号为 (i xor 1) 的边为残量网络,然后 (BFS) 就和正常的一样就可以了
namespace Edmonds_Karp {
#define MAXV 10001
#define MAXE 100001
int head[MAXV] = {0}, ver[MAXE], val[MAXE], nxt[MAXE], tot = 1;
void add(int x, int y, int v) {
ver[++ tot] = y, val[tot] = v, nxt[tot] = head[x], head[x] = tot;
ver[++ tot] = x, val[tot] = 0, nxt[tot] = head[x], head[x] = tot;
//构建残量网络 注意残量网络的初始剩余流量为0
}
int incf[MAXV], pre[MAXV], vis[MAXV];
//这次增广的最大流量,前驱
int s, t;//源点,汇点
queue<int>q;
bool bfs() {
memset(vis, 0, sizeof(vis));
q.push(s);
incf[s] = 1e9;//源点无限流量
register int x;
while(! q.empty()) {
x = q.front(), q.pop();
for(int i = head[x]; i; i = nxt[i])
if(val[i] && ! vis[ver[i]]) {
vis[ver[i]] = 1;
pre[ver[i]] = i;
incf[ver[i]] = min(incf[x], val[i]);
if(ver[i] == t)return 1;
q.push(ver[i]);
}
}
return 0;
}
int update() {//更新剩余流量
int p = t, i;
while(p != s) {
i = pre[p];
val[i] -= incf[t];
val[i ^ 1] += incf[t];
p = ver[i ^ 1];
}
return incf[t];
}
int EK() {
int ans = 0;
while(bfs()) ans += update();
return ans;
}
#undef MAXV
#undef MAXE
}
Dinic
我们可以发现,(EK) 算法每次只更新一条增广路,而一次 (BFS) 可能可以找出多条最短路,这时,我们就可以使用 (Dinic) 算法
每次 (BFS) 的时候我们将图分层,如果存在增广路,我们就在图上 (DFS) 来更新,每次更新的时候都只考虑在下一层的点,回溯的时候再将剩余流量更新
namespace Dinic {
#define MAXV 20001
#define MAXE 200001
int head[MAXV] = {0}, ver[MAXE], val[MAXV], nxt[MAXV], tot = 1;
void add(int x, int y, int v) {
ver[++ tot] = y, val[tot] = v, nxt[tot] = head[x], head[x] = tot;
ver[++ tot] = x, val[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
int d[MAXV];
int s, t;
queue<int>q;
bool bfs() {
memset(d, 0, sizeof(d));
register int x;
q.push(s);
d[s] = 1;
while(!q.empty()) {
x = q.front(), q.pop();
for(int i = head[x]; i; i = nxt[i])
if(val[i] && ! d[ver[i]]) {
d[ver[i]] = d[x] + 1;//分层
q.push(ver[i]);
}
}
return d[t];
}
int dfs(int x, int flow) {//流过这个点的最大可能的流量
if(x == t) return flow;
register int k, rest = flow;
for(int i = head[x]; i && rest; i = nxt[i])
if(val[i] && d[ver[i]] == d[x] + 1) {
k = dfs(ver[i], min(val[i], rest));
if(! k) d[ver[i]] = 0;//剪枝
rest -= k;
val[i] -= k;
val[i ^ 1] += k;
}
return flow - rest;
}
int dinic() {
int ans = 0, flow;
while(bfs()) while((flow = dfs(s, 1e9))) ans += flow;
return ans;
}
#undef MAXV
#undef MAXE
}
当前弧优化
用 (cur) 数组储存 (head) 数组的东西,将 (DFS) 中的 (i) 取引用
for(int &i = head[x]; i; i = nxt[i])
最小割
最大流最小割定理:一个网络中的最大流 = 最小割
证明:假设最大流可能大于最小割,那么在割去这些边后仍可以找到增广路,与源点和汇点不连通矛盾,所以最小割小于等于最大流;如果我们将跑完最大流之后剩余流量为 (0) 的边删去,则无法再找到一条从源点到汇点的路径,否则与不存在增广路相矛盾,于是我们只需要从源点出发,沿着残量网络 (BFS),所有没有被访问过的点与被访问过的点之间的边构成一个割,且这个割的边最大流量和为最大流
以上是关于网络流的主要内容,如果未能解决你的问题,请参考以下文章
此应用小部件片段中所有意图 (PendingIntents) 的逻辑流
VSCode自定义代码片段14——Vue的axios网络请求封装
VSCode自定义代码片段14——Vue的axios网络请求封装