前言
网络中的交换机能够实时的进行数据的接收转发,如果我们要从A地传送一组数据报到达B地,在不存在丢包的前提下一次分发B地最多能接收多少报文呢?这种网络模型可以利用水流的模拟来解决,学名称网络流。
算法介绍
总所周知,水往低处流。每个节点可以抽象成一个木桶,所以一次能通过的流量是有瓶颈的,根据木桶原理,我们每次可以流过的流量取决于前面最小的弧。这里很容易走进一个误区,贪心地每次去找下一条路径的最大的流过去,这是错误的,至于为什么很容易想到一些例子推翻,试想,一个水流到达一个分叉路假设水流足够肯定是两边都会有水灌下去。
于是,我们算法地核心思路是:
- 每次找一条增广路,然后灌水下去,至于灌的水为: a [ e . t o ] = m i n ( a [ e . f r o m ] , e . c a p − e . f l o w ) a[e.to]=min(a[e.from], e.cap-e.flow) a[e.to]=min(a[e.from],e.cap−e.flow),相当于之前的水的最大和当前的残余容量找一个最小的更新,这是木桶原理(水多了也只能接收我容量内的,水不够我容量的也只有那么多让你接收)。
- 这样最终到达汇点就说明找到一条增广路了,更新后,利用反向弧来减少由于这次的流量占据的流量,相当于缩小容量
- 当找不到增广路后显然找到最大流,算法结束
实现
#include <bits/stdc++.h>
using namespace std;
/**
* 最大流增广路EK算法
*/
namespace EK {
const int inf = 0x3f3f3f3f; //给一个最大的水流量
const int maxnn = (int)1e4+5;
struct Edge {
int from, to, cap, flow; //边的起点 边的终点 边的容量 边的流量
Edge(int a, int b, int c, int d):from(a), to(b), cap(c), flow(d) {}
};
int n, m; //图的点个数 正向边个数
vector<Edge> edge; //注:如果用数组要开2倍的空间 因为有反向边
vector<int> G[maxnn]; //每个起点对应的edge中的边含有的边号
int a[maxnn]; //从源点到当前点最多能够接收的水流量
int pre[maxnn]; //每次寻找一条增广路的前驱弧的编号 (反向边使用)
void init() {
for (int i = 0; i <= n; i++) G[i].clear();
edge.clear();
}
void add_edge(int u, int v, int cap) {
edge.push_back(Edge(u, v, cap, 0)); //正向弧
edge.push_back(Edge(v, u, 0, 0)); //反向弧
G[u].push_back(edge.size()-2); //索引表
G[v].push_back(edge.size()-1);
}
//类似于spfa优化bellman-ford的思想 只有更新过的边才重新考虑
int bfs(int s, int t) {
int flow = 0; //整个系统的最大流量
//每次for寻找一条增广路更新系统的流量
for(;;) {
memset(a, 0, sizeof(a)); //每次初始化为每个结点都没有流量
queue<int> q;
a[s] = inf; //源点含有巨量的水源
q.push(s);
while (!q.empty()) {
int cur = q.front(); q.pop(); //找到一个节点去更新下一个节点
for (int i = 0; i < G[cur].size(); i++) {
int s = G[cur][i]; //弧编号
Edge& e = edge[s]; //处理这条弧 如果可以灌水 则要更新为残余流量
if (!a[e.to] && e.cap > e.flow) { //如果弧对应的终点没灌水 并且还有残余就要灌水进去
a[e.to] = min(e.cap-e.flow, a[cur]); //每次灌进当前路的瓶颈水量
pre[e.to] = s; //记录前驱路径
q.push(e.to); //加入队列用来下一次更新
}
}
if (a[t]) break; //发现找到一条增广路马上把这次的流量做统计
}
if (!a[t]) break; //发现不存在增广路 即最大流
//更新最大流
for (int cur = t; cur != s; cur = edge[pre[cur]].from) { //反向更新残余流量
edge[pre[cur]].flow += a[t];
edge[pre[cur]^1].flow -= a[t]; //反向边
}
flow += a[t];
}
return flow;
}
}
int main() {
int n, m, s, t;
cin >> n >> m >> s >> t;
EK::n = n;
EK::m = m;
EK::init();
int u, v, cap;
for (int i = 0; i < m; i++) {
cin >> u >> v >> cap;
EK::add_edge(u, v, cap);
}
cout << EK::bfs(s, t) << endl;
return 0;
}