最大流 最小割
-
网络流的一些定义和性质
1.流网络(Flow Network)
流网络为一个有向图,所有边 都有一个容量(Capacity),用表示。另外规定了两个点:源点(Source)、汇点(Sink),分别为和
2.流
-
定义流量为 为 的函数,表示经过这条边的流量。
-
称为边的剩余容量。
-
整个网络的流量为
3.流量函数满足的性质:
-
容量限制: 流经某一条边的流量不得超过其容量,即:
-
流量守恒: 对于除了源点汇点的任意节点 ,满足:
-
斜对称性:每条边流量与其反向边流量之和为0,即:
-
最大流
1.描述
对于给定的流网络,要求求出一个流,使得最大。
2.几个定义
-
残量网络(Residual Network): 图中所有剩余容量大于0的边构成的子图称为残量网络,用表示。
-
增广路(Augmenting Path): 上一条从源点到汇点的路径称为增广路。
3.Edmonds–Karp 算法
Ford–Fulkerson 增广: 是一种通过寻找增广路求解最大流的方法,步骤如下:
-
1.寻找一条增广路,边集为。
-
2.令,对于每一条边,将 ,同时根据斜对称性,将 。
-
3.再次建立残量网络,重复1步骤,直至找不到增广路。
Edmonds–Karp 算法即按照上面Ford–Fulkerson 增广的步骤,使用BFS寻找增广路。算法的时间复杂度为
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long lld;
const int N = 20010;
const int M = 200010;
int nextt[M], head[N], to[M], cnt = 1;
int n, m, tot = 0, s, t;
lld w[M];
bool vis[N];
void add(int x, int y, lld z) {
nextt[++cnt] = head[x]; to[cnt] = y;
w[cnt] = z; head[x] = cnt;
}
int edge[N], nxt[N];
bool bfs() {
queue <int> q;
memset(vis, false, sizeof(vis));
q.push(s); vis[s] = true; tot = 0;
memset(edge, 0 ,sizeof(edge)); memset(nxt, 0, sizeof(nxt));
while(!q.empty()) {
int u = q.front(); q.pop();
if(u == t) return true;
for(int i = head[u]; i; i = nextt[i]) {
int v = to[i];
if(w[i] > 0 && !vis[v]) {
q.push(v);
edge[v] = i; nxt[v] = u;
if(!vis[v]) vis[v] = true;
}
}
}
return false;
}
lld EK() {
lld ans = 0;
while(bfs()) {
memset(vis, 0, sizeof(vis)); tot = 0;
lld wi = 1e10;
for(int i = t; i != s; i = nxt[i]) wi = min(wi, w[edge[i]]);
for(int i = t; i != s; i = nxt[i])
w[edge[i]] -= wi, w[edge[i] ^ 1] += wi;
ans += wi;
}
return ans;
}
int main() {
// freopen("data.in", "r", stdin);
scanf("%d%d%d%d", &n, &m, &s, &t);
for(int i = 1, u, v; i <= m; i++) {
lld w; cin >> u >> v >> w;
add(u, v, w); add(v, u, 0);
}
cout << EK() << endl;
return 0;
}
4.Dinic 算法
两个定义:
-
层次图(Level Graph): 对进行BFS分层,从源点出发经过的边数相同的点为同一层,记为节点的层数。分层之后保留到达下一层的边,删除同层之间的边从而得到层次图,即:
-
阻塞流(Blocking Flow): 在找到一个流,使得无法继续增广,则称为的阻塞流。
Dinic 算法过程:
-
1.在上进行BFS求出层次图。
-
2.在上求出一个阻塞流。
-
3.将阻塞流加入到流 中,更新残量网络。
-
4.重复执行1,2,3过程,直到无法找到阻塞流(层次图中到不连通)
复杂度
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long lld;
const int N = 10005;
const int M = 200005;
int nextt[M], head[N], to[M];
lld w[M];
int n, m, s, t, cnt = 1;
int dis[N], cur[N];
void add(int x, int y, lld z) {
nextt[++cnt] = head[x]; w[cnt] = z;
to[cnt] = y; head[x] = cnt;
}
bool bfs() {
queue <int> q;
memset(dis, 0, sizeof(dis));
q.push(s); dis[s] = 1;
bool flag = false;
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = head[u]; i; i = nextt[i]) {
int v = to[i];
if(w[i] > 0 && !dis[v]) {
dis[v] = dis[u] + 1; q.push(v);
if(v == t) flag = true;
}
}
}
return flag;
}
lld dfs(int u, lld mine) {
lld minedge = 0;
if(u == t) return mine;
for(int i = cur[u]; i; i = nextt[i]) {
int v = to[i]; cur[u] = i;
if(w[i] > 0 && dis[v] == dis[u] + 1) {
lld now = dfs(v, min(mine, w[i]));
minedge += now;
w[i] -= now; w[i ^ 1] += now;
mine -= now;
if(!mine) break;
}
}
return minedge;
}
void dinic() {
lld ans = 0;
while(bfs()) {
memcpy(cur, head, sizeof(head));
ans += dfs(s, 1e10);
}
cout << ans << endl;
}
int main() {
// freopen("data.in", "r", stdin);
cin >> n >> m >> s >> t;
for(int i = 1, u, v; i <= m; i++) {
lld z; cin >> u >> v >> z;
add(u, v, z); add(v, u, 0);
}
dinic();
return 0;
}
当前弧优化: DFS时,每次增广完一条路径之后,的容量所剩无几,下一次到达时直接选择跳过,从下一条边开始增广。在代码中,使用数组记录点第一条未使用的边。
-
最小割
1.几个定义
-
割: 又称s-t割,是一种点的划分方式,将所有点分为两个集合和,其中,
-
割的容量: 定义为所有从中的点出发到达中的点的边的容量之和,即:。
-
最小割: 求出一个割使得 最小。
2.最大流最小割定理
在任何网络中,最大流的值等于最小割的容量。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理