边的表示
1)
如果要把边单独存在一个buffer里面,并且每条边有一个唯一的编号。
那么通常将正反边存在一起,比如0,1,这样就可以用 id^1 来方便地访问反边了。
2)
也可以对每个节点,存一个 vector<Edge>
其中 rev 是在 to 节点的邻边列表中的编号。
可以只用 cap 记录剩余容量,这样就省掉 flow 了。
struct Edge {
int to, cap, rev;
}
3)
对有向边,初始反边的容量为0。
对于无向边,在加边的时候,设置反边的容量与正边相同就好了。
这样就允许流可以从两个方向通过。
Edmonds-Karp 算法
使用BFS来找增广路径。
算法上界
O(VE2)
,证明见《算法导论》
struct Edge {
int from, to, cap, flow;
Edge(int u, int v, int c, int f):from(u), to(v), cap(c), flow(f) {}
};
struct EK {
vector<Edge> edges;
vector<int> g[N];
int a[N], p[N];
void add_edge(int u, int v, int c) {
edges.push_back( Edge(u, v, c, 0) );
edges.push_back( Edge(v, u, 0, 0) );
int sz = edges.size();
g[u].push_back( sz-2 );
g[v].push_back( sz-1 );
}
int bfs(int s, int t) {
queue<int> q;
memset(a, 0, sizeof(a));
a[s] = INF;
q.push(s);
while ( !q.empty() ) {
int u = q.front(); q.pop();
for(int i=0;i<g[u].size();++i) {
int eid = g[u][i];
Edge& e = edges[eid];
if ( !a[e.to] && e.cap > e.flow ) {
p[e.to] = eid;
a[e.to] = min (a[e.from], e.cap - e.flow);
q.push(e.to);
}
}
if ( a[t] ) break;
}
if ( !a[t] ) return 0;
for ( int i = t; i != s; i = edges[p[i]].from ) {
edges[p[i]].flow += a[t];
edges[p[i]^1].flow -= a[t];
}
return a[t];
}
int GetMaxFlow(int s, int t) {
int MaxFlow = 0, tmp;
while ( tmp = bfs(s, t) ) MaxFlow += tmp;
return MaxFlow;
}
void reset(int n) {
edges.clear();
rep(i, 0, n) g[i].clear();
}
};
Dinic 算法
首先来看 EK 算法的一个性质,即每次找到的增广路的长度是非递减的。证明也可以去算导。
Dinic 就是一次性增广多条路径,完成一次增广后,增广路的长度至少增加1。
因此Dinic最多运行V次。并且因为使用了“记录current指针”(下面会具体讲解)的重要优化,每次DFS增广的复杂度为
O(VE)
,所以算法上界
O(V2E)
阻塞流(翻译自http://www.cs.cmu.edu/~15451/lectures/lec11/dinic.pdf )
考虑算法某个阶段,由BFS所构建的层次图
GL
。Level0 即 {s}。Level 1 包含所有
GR
中从s出发且剩余容量为正的边所指向的顶点。其他的层定义类似。一旦到达t,
GL
构建完成。
GL
中只含相邻层之间的边。
我们只关心
GL
中以增序访问各层的路径,把这样的路径叫做 proper path。每当我们找到并增广这样的路径的时候, 一条从 level i 到 level i + 1 的边会被饱和(或者说,删除)。因此,若干次增广后(最多m次),将
GL
中将不存在 proper path。这样在
GL
中得到的流叫做阻塞流。
阻塞流算法
对每个节点,我们保存它到下一层的边的列表,并维护一个current变量对应正在增广中的当前边。初始化时,current指向每个列表的第一条边。随着算法进行,current逐渐“前进”。我们会不再考虑那些current**经过**的边。
在算法中,当 DFS(v) 返回时,它会告诉我们其是否找到了一条增广路。如果找到了,我们立即返回给调用者,告诉“path found”。如果没有找到,则current移向下一条边。当current到达末尾时,返回 “no path found”。
为什么current经过的边不会再被考虑?
1)
一条边 (u , v) 被判定为 useless, 仅当 DFS(v) 返回 “no path found”。因为此时从v出发的所有边都被遍历了,所以从v出发不可能找到增广路了。
2)
一条边 (u , v) 被判定为 useless 的边在将来仍将是 useless。因为增广只会删除从一层到下一层的边,并增加相反方向的边。
// 网络流 dinic
struct Edge {
int to, cap, rev;
};
vector<Edge> G[MaxV+5];
int level[MaxV+5], iter[MaxV+5]; // 距离标号,当前边current
// 在残量网络里面加边
void add_edge(int from, int to, int cap) {
G[from].push_back( (Edge){to, cap, G[to].size()} );
G[to].push_back( (Edge){from, 0, G[from].size()-1} );
}
// bfs 构建层次
void bfs(int s) {
memset(level, -1, sizeof(level));
level[s] = 0;
queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front();q.pop();
int sz = G[u].size();
for (int i=0;i<sz;++i) {
Edge &e = G[u][i];
if (e.cap > 0 && level[e.to] < 0) {
level[e.to] = level[u] + 1;
q.push(e.to);
}
}
}
}
int dfs(int x, int t, int f) {
if (x == t || f == 0) return f;
int flow = 0, sz = G[x].size();
for (int &i = iter[x]; i<sz;++i) {
Edge &e = G[x][i];
if (e.cap > 0 && level[x] < level[e.to]) {
int d = dfs(e.to, t, min (f, e.cap));
if (d > 0) {
e.cap -= d;
G[e.to][e.rev].cap += d;
flow += d;
f -= d;
if (!f) break;
}
}
}
return flow;
}
int max_flow(int s, int t) {
int flow = 0;
for (;;) {
bfs (s);
if (level[t] < 0) return flow;
memset(iter, 0, sizeof(iter));
flow += dfs(s, t, inf);
}
return flow;
}