网络流学习笔记

今天重温了网络流,感觉收获颇丰
网络流其实可以抽象成流水问题,有n个点m条边,每条边有最大容量,有一个出水的源点S和进水的汇点T,问你最后汇点的水最多能有多少。

  • 增广路:从ST的一条路径中流的值都大于零的一条路就叫增广路

讲解法之前先介绍一下建反边的操作的意义:因为每次我们都会去寻找增广路但是我们并不知道当前这条是不是最优方案,因此我们需要一个反悔操作来帮我们找到最终答案,因此反边就出现了,反边的意思就是把原本是(u>v)的路径反建一条,使每次在流到这条路径的时候在反边上减去在正边中流的流量,这样就可以做到下次再寻找增广路的时候反悔了。

1|0EK

EK算法的核心就是每次在图中寻找一条增广路并更新流量,直到找不到新的增广路为止。时间复杂度是O(nm2)
代码:

点击查看代码
#include <iostream> #include <queue> using namespace std; typedef long long ll; const int N = 5e3 + 10; int n, m, s, t, tot = 1; ll ans; int pre[N], head[N]; ll dis[N]; bool vis[N]; struct zx{int to, nxt; ll w;} e[N * 2]; void add(int u, int v, ll w) { e[++tot] = {v, head[u], w}; head[u] = tot; } bool bfs() { for(int i = 1; i <= n; ++i) vis[i] = 0; queue < int > q; q.push(s), vis[s] = 1, dis[s] = 2147483647; while(!q.empty()) { int u = q.front(); q.pop(); for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; ll w = e[i].w; if(!w || vis[v]) continue; pre[v] = i, vis[v] = 1; dis[v] = min(dis[u], w); q.push(v); if(v == t) return 1; } } return 0; } void work() { int x = t; while(x != s) { int v = pre[x]; e[v].w -= dis[t]; e[v ^ 1].w += dis[t]; x = e[v ^ 1].to; } ans += dis[t]; } int main() { cin >> n >> m >> s >> t; for(int i = 1, u, v, w; i <= m; ++i) cin >> u >> v >> w, add(u, v, w), add(v, u, 0); while(bfs()) work(); cout << ans << endl; return 0; }

2|0Dinic

EK算法因为每次只寻找一条增广路所以时间复杂度比较高,如果在稠密图中就显得不优秀,所以每次寻找多个增广路的Dinic算法就出现了。
Dinic算法的思路是对于离源点距离一样的点建分层图,然后在回溯时更新边权。
但是这样还是略慢,怎么办?别急,我们有当前弧优化。
当前弧优化的意思就是一条路已经被增广过一次了,那么它就不会再被增广了,那其实在增广的时候完全不需要考虑它了,那我们就另开一个数组记录当前点需要从那个点向后遍历就行了。
时间复杂度是O(n2m),在稠密图中明显快于EK
代码:

点击查看代码
#include <iostream> #include <queue> using namespace std; typedef long long ll; const int N = 5e3 + 10, inf = 2147483647; int n, m, s, t, tot = 1; ll ans; int dfn[N], cur[N], head[N]; struct zx{int to, nxt, w;} e[N * 2]; void add(int u, int v, int w) { e[++tot] = {v, head[u], w}; head[u] = tot; } bool bfs() { for(int i = 1; i <= n; ++i) dfn[i] = inf; queue < int > q; q.push(s), dfn[s] = 0, cur[s] = head[s]; while(!q.empty()) { int u = q.front(); q.pop(); for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].to, w = e[i].w; if(dfn[v] != inf || !w) continue; q.push(v), dfn[v] = dfn[u] + 1, cur[v] = head[v]; if(v == t) return 1; } } return 0; } int dfs(int u, int k) { if(u == t) return k; int res = 0; for(int i = cur[u]; i && k; i = e[i].nxt) { int v = e[i].to, w = e[i].w; cur[u] = i; if(!w || (dfn[v] != dfn[u] + 1)) continue; int x = dfs(v, min(k, w)); if(!x) dfn[v] = inf; res += x, k -= x; e[i].w -= x, e[i ^ 1].w += x; } return res; } int main() { cin >> n >> m >> s >> t; for(int i = 1, u, v, w; i <= m; ++i) cin >> u >> v >> w, add(u, v, w), add(v, u, 0); while(bfs()) ans += dfs(s, inf); cout << ans << endl; return 0; }

以上是最大流的讲解。

3|0费用流:最小费用最大流

在考虑要最大流的同时需要使花费最小,显然普通的最大流完成不了这个,但是想要完成也很简单,只需要用SPFA或者dijkstra维护一下找增广路的过程就行了。
代码:

点击查看代码
#include <iostream> #include <cstring> #include <queue> using namespace std; typedef long long ll; const int N = 5e5; int n, m, s, t, tot = 1; ll maxg, minc; int pre[N], dis[N], mf[N], head[N]; bool vis[N]; struct zx{int to, nxt, w, dis;} e[N * 2]; void add(int u, int v, int w, int dis) { e[++tot] = {v, head[u], w, dis}; head[u] = tot; } bool spfa() { queue < int > q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); q.push(s), dis[s] = 0, vis[s] = 1, mf[s] = 1 << 30; while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = head[u]; i; i = e[i].nxt) { if(!e[i].w) continue; int v = e[i].to; if(dis[v] > dis[u] + e[i].dis) { dis[v] = dis[u] + e[i].dis; mf[v] = min(mf[u], e[i].w); pre[v] = i; if(!vis[v]) vis[v] = 1, q.push(v); } } } if(dis[t] == 1061109567) return 0; return 1; } void work() { maxg += mf[t], minc += mf[t] * dis[t]; int u = t; while(u != s) { int i = pre[u]; e[i].w -= mf[t], e[i ^ 1].w += mf[t]; u = e[i ^ 1].to; } } int main() { cin >> n >> m >> s >> t; for(int i = 1, u, v, w, dis; i <= m; ++i) cin >> u >> v >> w >> dis, add(u, v, w, dis), add(v, u, 0, -dis); while(spfa()) work(); cout << maxg << ' ' << minc << '\n'; return 0; }

__EOF__

本文作者Rose_Lu
本文链接https://www.cnblogs.com/roselu/p/18314719.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Rose_Lu  阅读(27)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2023-07-21 cdq分治
2023-07-21 平衡树专栏
点击右上角即可分享
微信分享提示