(点击此处查看原题)
Dinic算法
Dinic算法相对于EK算法,主要区别在于Dinic算法对图实现了分层,使得我们可以用一次bfs,一次dfs使得多条增广路得到增广
普通的Dinic算法已经可以处理绝大多数最大流(最小割)的题目了,但是总是有些题目会卡住普通的Dinic算法,此时我们就需要用到当前弧优化了
当前弧优化简述
不要小看当前弧优化,这个优化效果可是很明显的,就这个例题来说,我用普通的Dinic算法用时约1.7s,而使用了当前弧优化的Dinic算法后,只用了176ms,由此可以看出这个优化的强大
当前弧优化的核心思想为:避免遍历已经满流的边,总所周知,已经满流的边已经再构成增广路,而普通Dinic算法中,我们总是遍历所有的边,后判断这条边是否满流,这样相当于白白消耗时间
回想一下我们普通Dinic算法中将增广路增广的方法:我们总是尽可能地将某一条边的容量完全利用,因为我们建立了反边,可以“反悔”,因此我们可以完全利用这条边的容量;而对于被完全利用的边,因为这条边已经满流了,之后的增广路中不会用到这条边,那么我们就可以标记一下从每个点出发的第一条不满流边,下次从这个点开始求增广路的时候,就可以跳过之前已经满流的那些边,直接从不满流边开始遍历了
具体操作的话,我们用cur数组记录以每个点为起点的边当前可用的第一条不满流边,在dfs之前我们将head数组的值复制给cur数组,然后再dfs将增广路增广的时候,我们记录下以i为起点的第一条不满流边,即cur[i],因为我们从i继续向后增广的时候,只要流入流量flow_in用尽,当前枚举到的这条边之前的边都是满流的了,所以我们记录下最后一条遍历的边作为cur[i],下次遍历从这个点出发的边的时候,从cur[i]开始遍历即可。
代码区
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x, y) cout<<"["<<x<<","<<y<<"] " #define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll mod = 1e6 + 3; const int Max = 1e5 + 10; struct Edge { int to, next, flow; //flow记录这条边当前的边残量 }edge[Max << 1]; int n, m, s, t; int head[Max], tot; int dis[Max],cur[Max]; void init() { memset(head, -1, sizeof(head));tot = 0; } void add(int u, int v, int flow) { edge[tot].to = v; edge[tot].flow = flow; edge[tot].next = head[u]; head[u] = tot++; } bool bfs() //判断图是否连通 { queue<int>q; memset(dis, -1, sizeof(dis)); dis[s] = 0; q.push(s); while (!q.empty()) { int u = q.front();q.pop(); for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if (dis[v] == -1 && edge[i].flow > 0) //可以借助边i到达新的结点 { dis[v] = dis[u] + 1; //求顶点到源点的距离编号 q.push(v); } } } return dis[t] != -1; //确认是否连通 } int dfs(int u, int flow_in) { if (u == t) return flow_in; int flow_out = 0; //记录这一点实际流出的流量 for (int i = cur[u]; i != -1;i = edge[i].next) { cur[u] = i; //由于我们增广的时候都是尽量用尽这条边的容量为前提的, // 那么在在用尽流入流量之前,我们使用的边,都已经满流了 //没有继续增广的可能了,所以抛弃了这些重复的边,从未满流的边开始遍历 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].flow > 0) { int flow_part = dfs(v, min(flow_in, edge[i].flow)); if (flow_part == 0)continue; //无法形成增广路 flow_in -= flow_part; //流出了一部分,剩余可分配流入就减少了 flow_out += flow_part; //记录这一点最大的流出 edge[i].flow -= flow_part; edge[i ^ 1].flow += flow_part; //减少增广路上边的容量,增加其反向边的容量 if (flow_in == 0) break; } } return flow_out; } int max_flow() { int sum = 0; while (bfs()) { for(int i = 1;i <= n ;i ++) cur[i] = head[i]; sum += dfs(s, inf); } return sum; } int main() { #ifdef LOCAL //freopen("input.txt", "r", stdin); //freopen("output.txt", "w", stdout); #endif while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF) { init(); for (int i = 1, u, v, flow;i <= m; i++) { scanf("%d%d%d", &u, &v, &flow); add(u, v, flow);add(v, u, 0); } printf("%d ", max_flow()); } return 0; }