算法学习:最大流最小割
【定义】
【最大流】
从源点向连边流出流量 fi ,总计为 f,在到达汇点时,对每条边的流量限制ei都有,fi<ci
令 f 尽量大,这个 f 被称为最大流
【最小割】
有图 V,给出点 s,t,去掉一条边的代价为其流量限制,求使 s 无法到 t 的最小代价
这个代价被称为最小割
经过一些我看不懂(我会继续看的QAQ)证明,我们可以得到结论
【结论】 最大流等于最小割
【方法】
【Ford】
通过增加反向流量的方法,提供一种“反悔“的操作,从而支持流量从源点到汇点的改变和增加
新增加的路径被称为增广路
记得当时学匈牙利算法时候看过
#include<cstdio> #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int MAXN = 10010; const int MAXM = 200010; const int INF = (1 << 31) - 1; int n, m, s, t; struct V { int to; int rev; int cap; int nt; }edge[MAXM]; int st[MAXN], top=0; int vis[MAXN]; void add(int x, int y, int val) { top++; edge[top] = { y, top + 1, val, st[x] }; st[x] = top; top++; edge[top] = { x, top - 1, 0 , st[y] }; st[y] = top; } int dfs(int num, int srm) { if (num == t) return srm; vis[num] = 1; for (int i = st[num]; i!=-1; i = edge[i].nt) { int to = edge[i].to; if (!vis[to] && edge[i].cap > 0) { int minn = min(srm, edge[i].cap); int d = dfs(to, minn); if (d <= 0) continue; edge[i].cap -= d; edge[edge[i].rev].cap += d; return d; } } return 0; } int max_flow() { int flow = 0; while (1) { memset(vis, 0, sizeof(vis)); int d = dfs(s, INF); if (!d) break; flow += d; } return flow; } int main() { scanf("%d%d%d%d", &n, &m, &s, &t); memset(st, -1, sizeof(st)); for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z); } printf("%d", max_flow()); return 0; }
【dinic】
通过对节点进行分层的操作,从而使增广路的效率提高(使其不会向回运动)
同时,对走过的路进行标记(或者取消),通过节省路径数,提高效率
#include<cstdio> #include<queue> #include<cstring> #define ll long long using namespace std; const int MAXN = 10010; const int MAXM = 200010; const ll INF = (1ll << 62) - 1; struct note { int to; int nt; int rev; ll cal; }; struct edge { note arr[MAXM]; int st[MAXN]; int dis[MAXN]; int cur[MAXN]; int depth[MAXN]; int top; int n, m, s, t; edge() { memset(st, -1, sizeof(st)); memset(depth, -1, sizeof(depth)); memset(dis, -1, sizeof(dis)); top = 0; } void read() { top = 0; scanf("%d%d%d%d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int x, y; ll z; scanf("%d%d%lld", &x, &y, &z); add(x, y, z); } } bool dep() { queue<int> q; q.push(s); memset(depth, -1, sizeof(depth)); depth[s] = 0; while (!q.empty()) { int v = q.front(); q.pop(); for (int i = st[v]; i != -1; i = arr[i].nt) { int to = arr[i].to; if (!arr[i].cal) continue; if (depth[to] != -1) continue; depth[to] = depth[v] + 1; q.push(to); } } return (depth[t] != -1); } void add(int x, int y, ll z) { top++; arr[top] = { y,st[x],top + 1,z }; st[x] = top; top++; arr[top] = { x,st[y],top - 1,0 }; st[y] = top; } ll min_dis() { } ll dfs(int now, ll val) { if (now == t || !val) return val; ll flow = 0; for (int& i = cur[now]; i != -1; i = arr[i].nt) { int to = arr[i].to; if (depth[to] != depth[now] + 1) continue; ll f = dfs(to, min(arr[i].cal, val)); if (!f || !arr[i].cal) continue; flow += f; arr[i].cal -= f; arr[arr[i].rev].cal += f; val -= f; if (!val) return flow; } return flow; } ll dinic() { ll flow = 0; ll f; while (dep()) { for (int i = 1; i <= n; i++) cur[i] = st[i]; while (f = dfs(s, INF)) flow += f; } return flow; } }; edge road; int main() { int T; road.read(); printf("%lld", road.dinic()); return 0; }
注意:上面板子中需要更改的东西
read():可以理解为建图的函数
MAXN:点的个数
n,siz:这里需要区分出题目所给能够给出信息的点和之后建联通图之后获得的图上点的个数
INF:最大值,如果改变元素:cal的数据类型,则数据可能会因为之前LL 的 INF太大然后爆掉
【扩展】
汪聚聚上课说图论题大多数
要么是多种图论知识的结合,(比如多校有道先求最短路径然后在最短路径上跑最小割的
要么是隐式图,只要能够成功建立出相应的图,只需要跑个模板就可以了
而网络流通常都是,板子几乎不变,但是题的难度和建图难度相同的那种
以下是几种方法的举例和讲解:
2 . 拆流入流出,通过建图和网络流筛选不符合情况