@ZCDHJ
2018-09-28T13:41:43.000000Z
字数 5246
阅读 677
网络流
举个栗子:现有 根水管连接 个点,每根水管每秒通水量的限制为 ,那么求从点 输水到 一秒最多能输多少水,假设水的速度是无限快的。这样子的问题就是最简单的最大流问题。
我们设 两个点之间水管的容量为 , 为 实际流向 的水,那么 称为这条水管的剩余容量 。在任何时刻中,网络中剩余容量大于 的边构成的图叫做残量网络。
会满足下面的限制
上面例子里的 其实叫做源点, 叫做汇点。最大流问题就跟上面那个问题一样,求源点到汇点的最大流量。求解最大流问题有很多种算法,笔者比较菜,只会 EK 和 Dinic。
EK 算法,全称 Edmonds-Karp 增广路算法。其主要思路是使用 BFS 找到一条从 到 的路径,其路径上所有边的剩余容量都大于 。我们将这样的一条路径称之为增广路。很明显,这样子可能得不到最优解,所以要建反向边,相当于是可以撤销这条边的流量。
时间复杂度为 。
以下模板用来 AC Luogu 【模板】网络最大流
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
const int INF = 0x3f3f3f3f;
const int MAXN = 1e4;
const int MAXM = 1e5;
int n, m, s, t, edge = -1;
int firstEdge[MAXN | 1], minFlow[MAXN | 1], F[MAXN | 1];
struct Edge {
int to, w, nxt;
Edge(){}
Edge(int x, int y, int z) {
to = y;
w = z;
nxt = firstEdge[x];
}
} e[MAXM << 1 | 1];
inline int read() {
register int x = 0;
register char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return x;
}
inline void addEdge(int x, int y, int z) {
e[++edge] = Edge(x, y, z);
firstEdge[x] = edge;
}
bool Augment() {
bool vis[MAXN | 1];
std::queue <int> q;
memset(vis, 0, sizeof(vis));
q.push(s);
vis[s] = 1;
minFlow[s] = INF;
do {
int from = q.front();
q.pop();
for(int k = firstEdge[from]; k != -1; k = e[k].nxt) {
int to = e[k].to, w = e[k].w;
if(w > 0) {
if(vis[to] == true) continue;
minFlow[to] = std::min(minFlow[from], w);
F[to] = k;
q.push(to);
vis[to] = 1;
if(to == t) return true;
}
}
} while(!q.empty());
return false;
}
int EK() {
int res = 0;
while(Augment() == true) {
res += minFlow[t];
int tmp = t;
while(tmp != s) {
int k = F[tmp];
e[k].w -= minFlow[t];
e[k ^ 1].w += minFlow[t];
tmp = e[k ^ 1].to;
}
}
return res;
}
int main() {
n = read();
m = read();
s = read();
t = read();
memset(firstEdge, -1, sizeof(firstEdge));
for(int i = 1; i <= m; ++i) {
int u = read(), v = read(), w = read();
addEdge(u, v, w);
addEdge(v, u, 0);
}
printf("%d\n", EK());
return 0;
}
Dinic 算法主要思路也是寻找增广路。每次寻找增广路的时候,求出当前残量网络的 BFS 序,然后每次在残量网络上 DFS 找增广路从 点出发只能访问到 BFS 序为 的 BFS 序 的点。这样子的时间复杂度是 ,比较优秀。
以下模板用来 AC Luogu 【模板】网络最大流
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
const int INF = 0x3f3f3f3f;
const int MAXN = 1e4;
const int MAXM = 1e5;
int n, m, sp, ep, edge = -1;
int firstEdge[MAXN | 1], curE[MAXN | 1], depth[MAXN | 1];
struct Edge {
int to, w, nxt;
Edge(){}
Edge(int x, int y, int z) {
to = y;
w = z;
nxt = firstEdge[x];
}
} e[MAXM << 1 | 1];
inline int read() {
register int x = 0;
register char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return x;
}
inline void addEdge(int x, int y, int z) {
e[++edge] = Edge(x, y, z);
firstEdge[x] = edge;
}
bool getDepth() {
std::queue <int> q;
memset(depth, 0, sizeof(depth));
depth[sp] = 1;
q.push(sp);
do {
int from = q.front();
q.pop();
for(int k = firstEdge[from]; ~k; k = e[k].nxt) {
int to = e[k].to, w = e[k].w;
if(!depth[to] && w > 0) {
depth[to] = depth[from] + 1;
q.push(to);
}
}
} while(!q.empty());
return depth[ep] > 0;
}
int Augment(int x, int flow) {
if(x == ep) return flow;
for(int &k = curE[x]; ~k; k = e[k].nxt) {
int to = e[k].to, w = e[k].w;
if(depth[to] == depth[x] + 1 && w > 0) {
int tmp = Augment(to, std::min(flow, w));
if(tmp > 0) {
e[k].w -= tmp;
e[k ^ 1].w += tmp;
return tmp;
}
}
}
return 0;
}
int Dinic() {
int res = 0, tmp = 0;
while(getDepth()) {
for(int i = 1; i <= n; ++i) curE[i] = firstEdge[i];
while((tmp = Augment(sp, INF)) > 0) res += tmp;
}
return res;
}
int main() {
n = read();
m = read();
sp = read();
ep = read();
memset(firstEdge, -1, sizeof(firstEdge));
for(int i = 1; i <= m; ++i) {
int a = read(), b = read(), c = read();
addEdge(a, b, c);
addEdge(b, a, 0);
}
printf("%d\n", Dinic());
return 0;
}
给定一个网络 ,若某个 的子边集被删去,使得源点与汇点不再连通,则该边集称为网络的一个割。容量之和最小的割称为网络的最小割。
任何一个网络里的最大流与最小割的容量相等。
下面来理性感性理解一下:
为了使得源点与汇点不连通,每条源点通往汇点的路径上至少要删去一条边,也就是进入割集。那么贪心的想一想,肯定要删去容量最小的那条边。因为每条路径的流量取决于路径上容量最小的那条边,所以最小割=最大流。
在之前问题的基础上给每条边除容量外还加上了一个花费,其总花费为总流量乘以总花费。其中总花费最小的最大流称为最小费用最大流,而花费最大的称为最大费用最大流。二者合称费用流模型。费用流的前提是最大流。
我只会 EK 魔改的 SPFA
这个算法的思想就是在 EK 的基础下,找增广路的时候用 SPFA 找一条总费用最小的增广路,剩下的部分与 EK 差不多。
复杂度为 ,其中 为最大流量。
以下代码用来 AC Luogu 【模板】最小费用最大流
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
const int INF = 0x3f3f3f3f;
const int MAXN = 5e4;
const int MAXM = 5e5;
int n, m, sp, ep, edge = -1, maxFlow, minCost;
int firstEdge[MAXN | 1], minFlow[MAXN | 1], dist[MAXN | 1], lst[MAXN | 1];
struct Edge {
int to, f, w, nxt;
Edge(){}
Edge(int a, int b, int c, int d) {
to = b;
f = c;
w = d;
nxt = firstEdge[a];
}
} e[MAXM << 1 | 1];
inline int read() {
register int x = 0, v = 1;
register char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') v = -1;
ch = getchar();
}
while(isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * v;
}
inline void addEdge(int a, int b, int c, int d) {
e[++edge] = Edge(a, b, c, d);
firstEdge[a] = edge;
}
bool Augment() {
std::queue <int> q;
bool vis[MAXN | 1];
while(!q.empty()) q.pop();
memset(vis, false, sizeof(vis));
memset(dist, INF, sizeof(dist));
dist[sp] = 0;
minFlow[sp] = INF;
q.push(sp);
do {
int from = q.front();
q.pop();
vis[from] = false;
for(int k = firstEdge[from]; ~k; k = e[k].nxt) {
int to = e[k].to, w = e[k].w, f = e[k].f;
if(f > 0 && dist[to] > dist[from] + w) {
dist[to] = dist[from] + w;
minFlow[to] = std::min(minFlow[from], f);
lst[to] = k;
if(!vis[to]) {
q.push(to);
vis[to] = true;
}
}
}
} while(!q.empty());
return dist[ep] != INF;
}
void EK(int flow) {
while(flow > 0 && Augment()) {
maxFlow += minFlow[ep];
minCost += minFlow[ep] * dist[ep];
flow -= minFlow[ep];
int tmp = ep;
while(tmp != sp) {
e[lst[tmp]].f -= minFlow[ep];
e[lst[tmp] ^ 1].f += minFlow[ep];
tmp = e[lst[tmp] ^ 1].to;
}
}
}
int main() {
n = read();
m = read();
sp = read();
ep = read();
memset(firstEdge, -1, sizeof(firstEdge));
for(int i = 1; i <= m; ++i) {
int a = read(), b = read(), c = read(), d = read();
addEdge(a, b, c, d);
addEdge(b, a, 0, -d);
}
EK(INF);
printf("%d %d\n", maxFlow, minCost);
return 0;
}