一天攻克最大流算法

本贴最后更新于 2095 天前,其中的信息可能已经时过境迁

墙裂推荐这篇博客主要写的算法都正确,而且都有一些可取之处。

01:Edmonds-Karp 算法

EK 算法据说是最简单的。

但大家都是这么评价 EK 算法的:由于 EK 算法容易超时,所以这个在比赛中不怎么用。
可是我对最大流太懵逼了,还是从最简单、最辣鸡的开始吧!

稀里糊涂地模了一个板子,感觉就差不多了。

参考博客链接,理解不动就只能先放弃呗!

简单讲一下现在的理解:

  1. bfs 查找有无从源点 s 到汇点 t 的路,bfs 的过程中记录路径在 pre 数组中。

bfs 采用队列方式,需要另开一个 vis 数组避免重复遍历,然后只要到大汇点 t 就结束 bfs 返回 true,若 bfs 跑完仍然没有找到汇点 t 就返回 false,无路可走说明已经得到最大流。

2.bfs 失败说明已经找到最大流,结束循环。
若 bfs 返回 true,就遍历两次得到的路径。
第一次找到路径上的最小流,为该路径上的最大流,加入到结果中。
第二次将用过的边(u,v)减去本次的最大流,并建立反向边(v,u)大小为本次最大流。
while 循环结束时 return 结果 flow 就 OK。

思路简单,但理解的不是很彻底,勉强吧!

EK 算法板子/poj1273 Drainage Ditches(排水沟 AC 代码)

这道题目非常的水,这应该是我第一次在 poj 上看到自己的代码运行时间为 0ms

#include <iostream> #include <cstring> #include <cstdio> #include <queue> #define MAXN 205 #define INF 0x3f3f3f3f using namespace std; int net[MAXN][MAXN];//remain network, initialized as original graph int pre[MAXN]; int N, M; bool bfs(int s, int t)//find an augmented path from s to t { int now; queue<int> que; memset(pre, -1, sizeof(pre)); pre[s] = s; que.push(s); while(!que.empty()) { now = que.front(); que.pop(); for(int i = 1; i < N+1; i++) { if(net[now][i] && pre[i] == -1) { pre[i] = now; if(i == t) return true; que.push(i); }//end if }//end for }//end while return false; } int EdmondsKarp(int s, int t) { #define v pre[u] int flow = 0; while(bfs(s, t)) { int d = INF; for(int u = t; u != s; u = v) { d = min(d, net[v][u]); } for(int u = t; u != s; u = v) { net[v][u] -= d; net[u][v] += d; } flow += d; } return flow; #undef v } int main() { while(~scanf("%d%d", &M, &N)) { int a, b, d; memset(net, 0, sizeof(net)); for(int i = 1; i < M+1; i++) { scanf("%d%d%d", &a, &b, &d); net[a][b] += d;//pay attention to repetitive edges } printf("%d\n", EdmondsKarp(1, N)); } return 0; }

02:Dinic 算法

Dinic 算法在 EK 算法的基础上增加了分层图的概念。
就是计算一下每个点到源点 s 的 dis(就是边权值为 1 时的距离呗)

在普通情况下, DINIC 算法时间复杂度为 O()
在二分图中, DINIC 算法时间复杂度为 O()

这个现在还不懂。

HDU 3549 Flow ProblemDinicAC 代码

#include <iostream> #include <cstring> #include <cstdio> #include <queue> #define MAXN 100100 #define INF 0x3f3f3f3f using namespace std; struct edge{//chain forward star int to; int next; int val; }; edge Edge[MAXN]; int head[MAXN]; int cnt; void add_edge(int u, int v, int w) { Edge[cnt].to = v; Edge[cnt].next = head[u]; Edge[cnt].val = w; head[u] = cnt++; } int dis[MAXN]; bool bfs(int s, int t)//mark the no-weight distance to s (run once, move just 1) { queue<int> que; memset(dis, -1, sizeof(dis)); que.push(s); dis[s] = 0; while(!que.empty()) { int now = que.front(); que.pop(); for(int i = head[now]; i != -1; i = Edge[i].next)//Traverse all edges starting from now { int v = Edge[i].to; if(Edge[i].val && dis[v] == -1) { dis[v] = dis[now] + 1; if(v == t) return true; que.push(v); } } } return false; } int N, M; int dfs(int now, int left)//left: eesidual/剩余 flow { if(now == N) return left; int used = 0; for(int i = head[now]; i != -1; i = Edge[i].next)//Traverse all edges starting from s { int v = Edge[i].to; if(Edge[i].val && dis[v] == dis[now] + 1) { int d = dfs(v, min(left-used, Edge[i].val)); Edge[i].val -= d; Edge[i^1].val += d; used += d; if(used == left) return used; } } return used; } int Dinic(int s, int t) { int max_flow = 0; while(bfs(s, t)) max_flow += dfs(s, INF); return max_flow; } int main() { int Case; scanf("%d", &Case); int ct = 1; while (Case--) { memset(head, -1, sizeof(head)); cnt = 0; scanf("%d%d", &N, &M); int a, b, d; for (int i = 1; i < M+1; i++) { scanf("%d%d%d", &a, &b, &d); add_edge(a, b, d); add_edge(b, a, 0);//construct the reverse edge } printf("Case %d: %d\n", ct, Dinic(1, N)); ct++; } return 0; }

用 EK 算法也写了一份也 AC 了.

感觉现在看的例题都太入门了。

#include <iostream> #include <cstring> #include <cstdio> #include <queue> #define MAXN 1010 #define INF 0x3f3f3f3f using namespace std; int net[MAXN][MAXN];//remain network, initialized as original graph int pre[MAXN]; int N, M; bool bfs(int s, int t)//find an augmented path from s to t { int now; queue<int> que; memset(pre, -1, sizeof(pre)); pre[s] = s; que.push(s); while(!que.empty()) { now = que.front(); que.pop(); for(int i = 1; i < N+1; i++) { if(net[now][i] && pre[i] == -1) { pre[i] = now; if(i == t) return true; que.push(i); }//end if }//end for }//end while return false; } int EdmondsKarp(int s, int t) { #define v pre[u] int flow = 0; while(bfs(s, t)) { int d = INF; for(int u = t; u != s; u = v) { d = min(d, net[v][u]); } for(int u = t; u != s; u = v) { net[v][u] -= d; net[u][v] += d; } flow += d; } return flow; #undef v } int main() { int Case; scanf("%d", &Case); int ct = 1; while (Case--) { memset(net, 0, sizeof(net)); scanf("%d%d", &N, &M); int a, b, d; for (int i = 1; i < M+1; i++) { scanf("%d%d%d", &a, &b, &d); net[a][b] += d;//construct the reverse edge } printf("Case %d: %d\n", ct, EdmondsKarp(1, N)); ct++; } return 0; }

优化

多路增广

每次不是寻找一条增广路,而是在 DFS 中,只要可以就递归增广下去,实际上形成了一张增广网。

当前弧优化

对于每一个点,都记录上一次检查到哪一条边。因为我们每次增广一定是彻底增广(即这条已经被增广过的边已经发挥出了它全部的潜力,不可能再被增广了),下一次就不必再检查它,而直接看第一个未被检查的边。

优化之后渐进时间复杂度没有改变,但是实际上能快不少。
实际写代码的时候要注意,head 数组初始值为-1,存储时从 0 开始存储,这样在后面写反向弧的时候比较方便,直接异或即可。
关于复制 head 的数组 cur;目的是为了当前弧优化。已经增广的边就不需要再走了.
但是有自环的时候或者因为一些其他原因,cur 数组可能会导致 TLE,不信你试一下 HDU 3549 Flow Problem

上面纯属屁话,优化不会导致 TLE,但我还不知道那段代码为什么 TLE(可能时是数组不够大,但数组不够大怎么就 TLE 了呢,理解不懂就别理解了)。改了一下 dfs 的思想,成功就返回,下次再继续跑就行了,另外 auto 用在取 que.front()时可以加速几十秒,很好用,感觉很帅!

#include <iostream> #include <cstring> #include <cstdio> #include <queue> #define MAXN 2010 #define INF 1e9+7 using namespace std; struct edge{//chain forward star int to; int next; int val; }; edge Edge[MAXN]; int head[MAXN]; int cur[MAXN]; int cnt; void add_edge(int u, int v, int w) { Edge[++cnt].to = v; Edge[cnt].next = head[u]; Edge[cnt].val = w; head[u] = cnt; } int N, M; int dis[MAXN]; bool bfs(int s, int t)//mark the no-weight distance to s (run once, move just 1) { int i; queue<int> que; memset(dis, 0, sizeof(dis)); que.push(s); dis[s] = 1; while(!que.empty()) { auto now = que.front(); que.pop();//The auto here can speed up your code!!!! for(i = head[now]; i; i = Edge[i].next)//Traverse all edges starting from now { int v = Edge[i].to, c = Edge[i].val; if(c && !dis[v]) { dis[v] = dis[now] + 1; que.push(v); } } } return dis[t]; } int dfs(int now, int left)//left: eesidual/剩余 flow { if(now == N || !left) return left; for(int &i = cur[now]; i; i = Edge[i].next)//Traverse all edges starting from s { int v = Edge[i].to, c = Edge[i].val; if(c && dis[v] == dis[now] + 1) { int d = dfs(v, min(left, c)); if(d) { Edge[i].val -= d; Edge[i^1].val += d; return d; } } } return 0; } int Dinic(int s, int t) { int max_flow = 0; while(bfs(s, t)) { int val; for(int i = 1; i < N+1; i++) cur[i] = head[i]; while((val = dfs(s, INF))) max_flow += val; } return max_flow; } int main() { int Case; scanf("%d", &Case); int ct = 1; while (Case--) { memset(head, -1, sizeof(head)); cnt = 1; scanf("%d%d", &N, &M); int a, b, d; for (int i = 1; i < M+1; i++) { scanf("%d%d%d", &a, &b, &d); add_edge(a, b, d); add_edge(b, a, 0);//construct the reverse edge } printf("Case %d: %d\n", ct, Dinic(1, N)); ct++; } return 0; }

推送-重贴标签算法

在基于增广路径或线性规划的最大流问题的解中,非常适合

Hopcroft-Karp 二分匹配算法

目前最好的解决最大二分匹配问题的算法

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...