网络流
无内鬼,来点网络流
网络流是啥?
给你一个有向图 \(G = \{V, E\}\),它的每条边 \((u,v) \in E\) 都有一个非负流量 \(c(u,v)\),而每条不属于其的边的流量为 \(0\)。同时,该图上有两个特殊点:源点 \(s\) 与汇点 \(t\) 。
一个网络流 \(f : E \rightarrow R\) 是满足如下特性的函数:
- 容量限制:\(\forall (u,v) \in E,\ f(u,v) \le c(u,v)\)。
这点体现了一条边的流不能超过其流量。 - 斜对称:\(f(u,v) = -f(v,u)\)。
这点在换流的时候有用。 - 流守恒:\(\forall u \in V, u \neq s, u\neq t,\ \large\sum_{v \in V} f(u,v) = 0\)。
任意非源汇点的点净流为0。
定义一个流的流量为 \(\text{Max Flow} = \sum_{v\in V} f(s, v) = \sum_{v\in V} f(v, t)\)
最大流
字面意思,求源点到汇点的最大流量。
Folk-Fulkerson算法
我们有一个简单贪心思路:找到一条 \(s\rightarrow t\) 且流量非空的路径,把它上面的流量拉满,计入答案。这个路径就是所谓增广路,增广了原有的流。然而,当我们将一条不在最大流中的边增广了,那么就会出现问题,陷入局部最优解。为此,我们需要进行可反悔贪心。
我们考虑斜对称的作用。将原图的边变成双向边,并定义反向边上增加1流量就等于原边上减少1流量。这样,我们就能通过流量抵消获得单向边的实际流量。这样,非最优决策就会被退出,以此重新决策。
引入一个概念:残量网络。残量网络是一个原图 \(G\) 的子图 \(G' = \{ V', E'\}\),其中每条边的边权都为 \(c'(u,v) = c(u,v) - f(u,v)\)。这样,加载流量的操作就可以转化成残量网络中边权降低,流量抵消的操作就是边权增加。
我们建立流量网络时,记录每条边的出入节点、当前流量、反向边,随后不断跑dfs,每次走剩余容量非0的边来寻找增广路,维护路径上最小流量。dfs返回时执行可反悔贪心,减少当前边剩余容量, 增加反向边剩余容量,最终累加答案。dfs返回一个非负数表示所找到增广路的可用流量,若为0则无增广路,返回/若大于0则缩小剩余流量,返回答案。单次执行的复杂度为 \(O(V + E)\)。
然而这种算法的复杂度是 \(O(值域)\) 的。 不加掩饰,直接干上指数
考虑如何优化求解。
Edmond-Karp算法
我们考虑每次增广最短的增广路。这样的复杂度是 \(O(VE^2)\) 的。
稍微证明一下:增广是选出一条路上流最小的边,把它断掉。每次选择最短路保证了每次断掉目前最短的路,因此最短路单增。断掉的边不一定不再更新,但是更新它时最短路一定增加,而最短路最长也就 \(V\),因此最坏复杂度为 \(O(VE)\)。这最短路是01最短路,使用bfs维护,单次复杂度 \(O(E)\),因此总复杂度 \(O(VE^2)\)。
大概这么写?
int bfs(int st, int ed) {
rep(i,1,n) pre[i] = -1, flow[i] = inf;
queue<int> q;
pre[st] = 0, q.push(st);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = 1; i <= n; i++) {
if (i == st or pre[i] != -1 or g[u][i] == 0) continue;
pre[i] = u;
flow[i] = min(flow[u], g[u][i]);
q.push(i);
}
} if (flow[ed] == inf) return -1;
return flow[ed];
}
int EK() {
int ans = 0;
while (1) {
int k = bfs(S, T);
if (k == -1) break;
ans += k;
int ptr = T;
while (ptr != S) {
c[pre[ptr]][ptr] -= k, c[ptr][pre[ptr]] += k;
ptr = pre[ptr];
}
} return ans;
}
说句闲话,EK算法的时间复杂度重写一下是 \(O(V^5)\) 的。因为它每次遍历残量网络后只会找到一条增广路,很退化,考虑如何加速求解。
Dinic算法
Dinic算法引入了“分层图”的概念。具体地,我们按照每个点到源点的最短路长度分组,每组设为一层,最终只保留层间的边。这样的好处在于每条分层图上的边都定在一条残量网络的最短路上。因此,我们可以放心大胆地在分层图上跑dfs,进行多路增广。这样跑出来的结果肯定正确。而且,通过多路增广,我们可以保证一条边只会被增广一次,这又叫做当前弧优化。
其时间复杂度是 \(O(V^2E)\) 的。证明一下:每轮增广最少删一条边,因此总增广次数是 \(O(V)\) 的。每次dfs,由于当前弧优化,复杂度是 \(O(VE)\) 的。总复杂度是 \(O(V^2E)\) 的。
这个复杂度松得离谱。一般而言,如果你有一个 \(V = 1000, E = 10000\) 的图,你可以放心大胆地扔上去Dinic。而且法律规定不能卡Dinic
Dinic
typedef long long ll;
#define v e[i].to
#define d e[i].dis
#define d_pair e[i ^ 1].dis
int head[2][N], mlc = 1;
struct ep {
int to, next;
ll dis;
} e[N<<1];
inline void adde(int f, int t, ll fl) {
e[++mlc].to = t;
e[mlc].next = head[0][f];
e[mlc].dis = fl;
head[0][f] = mlc;
}
queue<int> q;
int dep[N];
bool bfs(int s, int t) {
rep(i,1,n) dep[i] = 0, head[1][i] = head[0][i];
q.push(s), dep[s] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (register int i = head[0][u]; i; i = e[i].next) {
if (d and !dep[v]) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
} return dep[t];
}
ll dfs(int u, ll in, int t) {
if (u == t) return in;
ll out = 0;
for (register int & i = head[1][u]; i; i = e[i].next) {
if (d and dep[v] == dep[u] + 1) {
ll res = dfs(v, min(d, in), t);
if (res <= 0) dep[v] = 0;
d -= res;
d_pair += res;
in -= res;
out += res;
if (in == 0) break;
}
} return out;
}
ll Dinic(int s, int t) {
ll ans = 0;
while (bfs(s, t)) ans += dfs(s, 1e18, t);
return ans;
}
adde(t1, t2, t3);
adde(t2, t1, 0);
关于 \(O(V^{1.5}E)\) 的 Dinic,可以看这里。
HLPP
我不会。
费用流
考虑现在不仅需要求出一张图的最大流。
每条边现在有一个单位费用 \(w_{(i,j)}\),当 \((i,j)\) 这条边流过了 \(f_{(i,j)}\) 的流量时,这条边的费用是 \(w_{(i,j)}\times f_{(i,j)}\)。当获得最大流时需要求出最小费用,这就是最小费用最大流。同时类似地有上下界最大流等一系列对流量存在恶心限制的流题。
这里以最小费用流为例列举一些常见的费用流算法。
EK费用流
现在 EK 不退化了
我们需要首先跑出最大流,在它的基础上跑出最小费用,因此该怎么增广还是怎么增广。
考虑一条增广路 \(p\) 的答案是 \(\sum_{(u,v) \in p}f_p\times w(i,j) = f_p\times \sum_{(u,v) \in p}w(i,j)\),即流量乘以将费用看做距离后的增广路长度,因此我们可以考虑贪心,每次找单位费用之和最小的增广路来增广。这样保证了任意时刻增广出的流都是最小费用流。
考虑现在反向边的费用。
由于反向边增流等于是正向边减流,因此费用也应当是正向边的相反数。
我们发现这里负边权无法避免,所以考虑一个SPFA。
复杂度……把bfs寻找最短路换成了跑spfa得到最短路,所以这部分的时间上界由 \(O(E)\) 上升到了 \(O(VE)\)。总时间复杂度自然变为 \(O(V^2E^2)\)。
当然这个上界就更松了
听说 \(\text{M}\color{red}{\text{in_25}}\) 能把EK卡到上界。
Dinic费用流
EK 又退化了
当然和 EK 类似,你把 bfs 换成 spfa 就行了。
Dinic费用流
const int N = 2e5 + 10;
int n, m, S, T, t1, t2, t3, t4, tot_cost;
#define v e[i].to
#define d e[i].flow
#define w e[i].wt
#define d_pair e[i ^ 1].flow
int head[2][N], mlc = 1;
struct ep {
int to, next, flow, wt;
} e[N<<1];
inline void adde(int f, int t, int fl, int wt) {
e[++mlc].to = t;
e[mlc].next = head[0][f];
e[mlc].flow = fl;
e[mlc].wt = wt;
head[0][f] = mlc;
}
queue<int> q;
int dis[N]; bool vis[N];
bool spfa(int s, int t) {
rep(i,1,n) dis[i] = INT_MAX, vis[i] = false, head[1][i] = head[0][i];
q.push(s); vis[s] = 1, dis[s] = 0;
while (!q.empty()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (register int i = head[0][u]; i; i = e[i].next) {
if (d and dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) vis[v] = 1, q.push(v);
}
}
} return dis[t] != INT_MAX;
}
int dfs(int u, int in, int t) {
if (u == t) return in;
vis[u] = 1;
int out = 0;
for (register int & i = head[1][u]; i; i = e[i].next) {
if (!vis[v] and d and dis[v] == dis[u] + w) {
int res = dfs(v, min(d, in), t);
if (res > 0) tot_cost += res * w, d -= res, d_pair += res, out += res, in -= res;
if (in == 0) break;
}
} vis[u] = 0;
return out;
}
int Dinic(int s, int t) {
int ret = 0, tmp;
while (spfa(s, t)) while (tmp = dfs(s, INT_MAX, t)) ret += tmp;
return ret;
}
Primal-Dual
和 Johnson 最短路的思路类似,都是加个势能把边权转正然后跑dij。
首先跑一遍spfa,每个点 \(i\) 的势能是它到原点的最短路长度 \(h_i\)。然后 \((u,v)\) 的边权就转为 \(w_(u,v) + h_u - h_v\)。这样建出来的边权一定非负,并且在新图上的最短路就是原图上的最短路。证明详见 Johnson 最短路。
但每次增广后图的形态会发生变化,因此需要动态更新势能大小。
设增广后原点到 \(i\) 点的距离为 \(d_i\),则 \(h_i\leftarrow h_i+d_i\) 即可。
然后一遍遍跑 dij 就行。记得稠密图别用堆优化。
建图求解
板板
Dinic 最大流
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, n, m;
int head[2][N], mlc = 1;
struct ep {
int to, next, flow;
} e[N << 1];
void adde(int u, int v, int f, bool __same = false) {
e[++ mlc] = { v, head[0][u], f };
head[0][u] = mlc;
e[++ mlc] = { u, head[0][v], __same ? f : 0 };
head[0][v] = mlc;
}
int dep[N];
bool bfs(int s, int t) {
rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
queue<int> que;
que.push(s); dep[s] = 1;
while (que.size()) {
int u = que.front(); que.pop();
for (int i = head[0][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and !dep[v]) {
dep[v] = dep[u] + 1;
if (v == t) return 1;
que.push(v);
}
}
} return dep[t];
}
int dfs(int u, int in, int t) {
if (u == t or in <= 0) return in;
int out = 0;
for (int &i = head[1][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and dep[v] == dep[u] + 1) {
int res = dfs(v, min(in, e[i].flow), t);
in -= res, out += res;
e[i].flow -= res, e[i ^ 1].flow += res;
if (in <= 0) break;
}
}
if (out <= 0) dep[u] = 0;
return out;
}
int Dinic(int s, int t) {
int ret = 0, fl;
while (bfs(s, t)) while (fl = dfs(s, inf, t)) ret += fl;
return ret;
}
Dinic 最小费用最大流
int M, s, t, n, m, t1, t2, t3, t4;
int head[2][N], mlc = 1;
struct ep {
int to, next, flow, cost;
} e[N << 2];
void adde(int u, int v, int fl, int cs, bool __same = false) {
e[++ mlc] = { v, head[0][u], fl, cs };
head[0][u] = mlc;
e[++ mlc] = { u, head[0][v], (__same ? fl : 0), -cs };
head[0][v] = mlc;
}
int dis[N]; bool vis[N];
bool spfa(int s, int t) {
rep(i,1,M) dis[i] = inf, vis[i] = false, head[1][i] = head[0][i];
queue<int> que;
dis[s] = 0, vis[s] = 1, que.emplace(s);
while (que.size()) {
int u = que.front(); que.pop(); vis[u] = 0;
for (int i = head[0][u], v, w; i; i = e[i].next) {
v = e[i].to, w = e[i].cost;
if (e[i].flow and dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) que.emplace(v), vis[v] = 1;
}
}
} return dis[t] != inf;
}
int min_cost;
int dfs(int u, int in, int t) {
if (u == t or in <= 0) return in;
vis[u] = 1;
int out = 0;
for (int i = head[1][u], v, w, res; i and in > 0; i = e[i].next) {
head[1][u] = i, v = e[i].to, w = e[i].cost;
if (!vis[v] and e[i].flow and dis[v] == dis[u] + w) {
res = dfs(v, min(in, e[i].flow), t);
if (res > 0) {
e[i].flow -= res, e[i ^ 1].flow += res;
in -= res, out += res;
min_cost += res * w;
if (in <= 0) break;
}
}
} vis[u] = 0;
return out;
}
int Dinic(int s, int t) {
int ret = 0, fl;
while (spfa(s, t)) while (fl = dfs(s, inf, t)) ret += fl;
return ret;
}
Dinic 有源汇上下界最大流
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, S, T, n, m, ans, t1, t2, t3, t4;
int head[2][N], mlc = 1;
struct ep {
int to, next, flow;
} e[N << 1];
void __Adde(int u, int v, int f) {
e[++ mlc] = { v, head[0][u], f };
head[0][u] = mlc;
e[++ mlc] = { u, head[0][v], 0 };
head[0][v] = mlc;
}
int d[N], sumdeg;
void adde(int u, int v, int Min, int Max) {
__Adde(u, v, Max - Min);
d[u] -= Min, d[v] += Min;
}
int dep[N];
bool bfs(int s, int t) {
rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
queue<int> que;
que.push(s); dep[s] = 1;
while (que.size()) {
int u = que.front(); que.pop();
for (int i = head[0][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and !dep[v]) {
dep[v] = dep[u] + 1;
if (v == t) return 1;
que.push(v);
}
}
} return dep[t];
}
int dfs(int u, int in, int t) {
if (u == t) return in;
int out = 0;
for (int &i = head[1][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and dep[v] == dep[u] + 1) {
int res = dfs(v, min(in, e[i].flow), t);
if (res == 0) dep[v] = 0;
in -= res, out += res;
e[i].flow -= res, e[i ^ 1].flow += res;
if (in == 0) break;
}
} return out;
}
int Dinic(int s, int t) {
sumdeg = 0;
rep(i,1,M) {
if (i == S or i == T) continue;
if (d[i] > 0) __Adde(S, i, d[i]), sumdeg += d[i];
else if (d[i] < 0) __Adde(i, T, -d[i]);
} adde(t, s, 0, inf);
int ret = 0;
while(bfs(S, T)) ret += dfs(S, inf, T);
if (ret != sumdeg) return -1;
ret = 0;
while(bfs(s, t)) ret += dfs(s, inf, t);
return ret;
}
Dinic 有源汇上下界最小流
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, S, T, n, m, ans, t1, t2, t3, t4;
int head[2][N], mlc = 1;
struct ep {
int to, next, flow;
} e[N << 1];
void __Adde(int u, int v, int f) {
e[++ mlc] = { v, head[0][u], f };
head[0][u] = mlc;
e[++ mlc] = { u, head[0][v], 0 };
head[0][v] = mlc;
}
int d[N];
void adde(int u, int v, int Min, int Max) {
__Adde(u, v, Max - Min);
d[u] -= Min, d[v] += Min;
}
int dep[N];
bool bfs(int s, int t) {
rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
queue<int> que;
que.push(s); dep[s] = 1;
while (que.size()) {
int u = que.front(); que.pop();
for (int i = head[0][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and !dep[v]) {
dep[v] = dep[u] + 1;
if (v == t) return 1;
que.push(v);
}
}
} return dep[t];
}
int dfs(int u, int in, int t) {
if (u == t) return in;
int out = 0;
for (int &i = head[1][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and dep[v] == dep[u] + 1) {
int res = dfs(v, min(in, e[i].flow), t);
if (res == 0) dep[v] = 0;
in -= res, out += res;
e[i].flow -= res, e[i ^ 1].flow += res;
if (in == 0) break;
}
} return out;
}
int Dinic(int _s, int _t) {
rep(i,1,M) {
if (i == S or i == T) continue;
if (d[i] > 0) __Adde(S, i, d[i]);
else if (d[i] < 0) __Adde(i, T, -d[i]);
}
while(bfs(_s, _t)) dfs(_s, inf, _t);
adde(t, s, 0, inf);
while(bfs(_s, _t)) dfs(_s, inf, _t);
return e[mlc].flow;
}
Dinic 有源汇上下界可行流
int M, s, t, S, T, n, m;
int head[2][N], mlc = 1;
struct ep {
int to, next, flow;
} e[N << 1];
void __Adde(int u, int v, int f) {
e[++ mlc] = { v, head[0][u], f };
head[0][u] = mlc;
e[++ mlc] = { u, head[0][v], 0 };
head[0][v] = mlc;
}
int d[N];
void adde(int u, int v, int Min, int Max) {
__Adde(u, v, Max - Min);
d[u] -= Min, d[v] += Min;
}
int dep[N];
bool bfs(int s, int t) {
rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
queue<int> que;
que.push(s); dep[s] = 1;
while (que.size()) {
int u = que.front(); que.pop();
for (int i = head[0][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and !dep[v]) {
dep[v] = dep[u] + 1;
if (v == t) return 1;
que.push(v);
}
}
} return dep[t];
}
int dfs(int u, int in, int t) {
if (u == t) return in;
int out = 0;
for (int &i = head[1][u], v; i; i = e[i].next) {
v = e[i].to;
if (e[i].flow and dep[v] == dep[u] + 1) {
int res = dfs(v, min(in, e[i].flow), t);
if (res == 0) dep[v] = 0;
in -= res, out += res;
e[i].flow -= res, e[i ^ 1].flow += res;
if (in == 0) break;
}
} return out;
}
int sumdeg;
int Dinic(int _s = S, int _t = T) {
int ret = 0;
rep(i,1,M) {
if (i == S or i == T) continue;
if (d[i] > 0) __Adde(S, i, d[i]), sumdeg += d[i];
else if (d[i] < 0) __Adde(i, T, -d[i]);
}
__Adde(t, s, inf);
while(bfs(_s, _t)) ret += dfs(_s, inf, _t);
return ret;
}
Dinic 有源汇最小费用可行流
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, S, T, n, m, ans, t1, t2, t3, t4;
int head[2][N], mlc = 1;
struct ep {
int to, next, flow, cost;
} e[N << 2];
void __Adde(int u, int v, int fl, int cs, bool __same = false) {
e[++ mlc] = { v, head[0][u], fl, cs };
head[0][u] = mlc;
e[++ mlc] = { u, head[0][v], (__same ? fl : 0), -cs };
head[0][v] = mlc;
}
int d[N], min_cost;
void adde(int u, int v, int Min, int Max, int cost) {
__Adde(u, v, Max - Min, cost);
d[u] -= Min, d[v] += Min, min_cost += cost * Min;
}
int dis[N]; bool vis[N];
bool spfa(int s, int t) {
rep(i,1,M) dis[i] = inf, vis[i] = false, head[1][i] = head[0][i];
queue<int> que;
dis[s] = 0, vis[s] = 1, que.emplace(s);
while (que.size()) {
int u = que.front(); que.pop(); vis[u] = 0;
for (int i = head[0][u], v, w; i; i = e[i].next) {
v = e[i].to, w = e[i].cost;
if (e[i].flow and dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) que.emplace(v), vis[v] = 1;
}
}
} return dis[t] != inf;
}
int dfs(int u, int in, int t) {
if (u == t or in <= 0) return in;
vis[u] = 1;
int out = 0;
for (int i = head[1][u], v, w, res; i and in > 0; i = e[i].next) {
head[1][u] = i, v = e[i].to, w = e[i].cost;
if (!vis[v] and e[i].flow and dis[v] == dis[u] + w) {
res = dfs(v, min(in, e[i].flow), t);
if (res > 0) {
e[i].flow -= res, e[i ^ 1].flow += res;
in -= res, out += res;
min_cost += res * w;
if (in <= 0) break;
}
}
} vis[u] = 0;
return out;
}
int Dinic(int _s, int _t) {
rep(i,1,M) {
if (i == S or i == T) continue;
if (d[i] > 0) __Adde(S, i, d[i], 0);
else if (d[i] < 0) __Adde(i, T, -d[i], 0);
}
// while(bfs(_s, _t)) dfs(_s, inf, _t);
adde(t, s, 0, inf, 0);
while(spfa(_s, _t)) dfs(_s, inf, _t);
return min_cost;
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/network-flow.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。