P3376 【模板】网络最大流
题意
求网络最大流.
首先介绍一下网络流的基本概念. 一个带权有向图, 边权称为容量. 其中一个只有出度的点, 源点, 一个只有入度的点, 汇点.
流从源点源源不断地产生, 在汇点消失. 边的容量就是能它流过的最大流, 实际流过的流成为边的流量. 源点流出的最大的流一定和汇点消失的最大的流相等, 我们把这个最大流称为这个网络的流.
残量网络
有流流过网络时, 边的剩余剩余流量构成的网络, 称残量网络.
一条边本没有反向容量, 但是由于有流正向流过时, 相当于给这条边减少了反向流量. 反向流量为负数, 反向容量为 , 所以就出现了反向余量.
为了记录反向流量, 我们给每一条边另开一条反向的容量为 的边, 以完整存储残量网络.
增广路
残量网络里, 一条从源点通往汇点的路径, 称为增广路, 即可以增加当前网络流量的路径. 只要找到一条增广路, 就可以更新残量网络.
Edmonds-Karp
基于 BFS 的算法, 一次BFS可找到一条长度最小 (经过边最少) 的增广路, 更新答案, 并且每次记录增广路, 从汇点反向回溯到源点, 更新残量网络. 当残量网络中源点到汇点不连通时, 说明已经没有增广路可找了, 算法结束.
Dinic
DFS 和 BFS 结合, 每轮可找多条增广路. 用 DFS 搜索时, 会尝试将流入一个节点的流全部流出. 但是只有 DFS 会导致在正反向边反复流. 所以用 BFS 给残量网络分层, 每次只能顺着分层图走, 防止回头.
实现
两种算法的框架相似, 所以部分可能会合并.
连边
本题规模可以允许邻接矩阵去重边, 提高算法主体效率.
void Lnk(Node *x /*起点*/, Node *y /*终点*/, const long long &z /*容量*/) {
if (x->Fst[y - N]) { //邻接矩阵去重边
x->Fst[y - N]->Mx += z; //重边叠加容量合并成一条
return;
}
x->Fst[y - N] = Cnte; //新边(内存池中取用)
Cnte->To = y; //终点
Cnte->Mx = z; //容量
(Cnte++)->Nw = 0; //流量为零
return;
}
主函数 main
int main() {
n = RD();
m = RD();
S = RD() + N;
T = RD() + N;
memset(N, 0, sizeof(N)); //输入和初始化
for (register unsigned int i(1); i <= m; ++i) { //连边
A = RD() + N;
B = RD() + N;
C = RD();
Lnk(A, B, C);
Lnk(B, A, 0);
}
for (register unsigned int i(1); i <= n; ++i) { //邻接矩阵存入邻接表
N[i].Cntne = 0; //邻接表栈顶
for (register unsigned int j(1); j <= n; ++j) {
if (N[i].Fst[j]) { //存在一条边
N[i].Scd[++N[i].Cntne] = N[i].Fst[j]; //存入邻接表
}
}
}
Edmonds_Karp/Dinic();//算法主体
printf("%llu\n", Ans);
return 0;
}
Edmonds-Karp
算法主体, 负责调用 Fnd
(找增广路), Bk
(更新残量网络) 和初始化
void Edmonds_Karp() {
while (1) {
memset(NodeFl, 0, sizeof(NodeFl));
memset(Q, 0, sizeof(Q));
memset(Cm, 0, sizeof(Cm)); //初始化数组
hd = 0;
tl = 1;
Q[hd] = S; //初始化队列 (BFS 用)
NodeFl[S - N] = 0x3f3f3f3f3f3f3f3f; //初始化源点入量 (源点入量无穷)
Fnd(); //找一条增广路
if (NodeFl[T - N]) { //找到了
ans += NodeFl[T - N]; //统计入答案
Bk(); //更新残量网络
} else {
break; //找不到, 结束算法
}
}
return;
}
单条增广路 Fnd
这是 Edmonds-Karp
的一部分, 采用 BFS 的方式, 找到就返回.
void Fnd() {
Node *x;
while (hd < tl) {
x = Q[hd++]; //取队首
for (register unsigned int i(1); i <= x->Cntne; i++) {
if ((!NodeFl[x->Scd[i]->To - N] /*目标无入量 (本次没搜过)*/) &&
x->Scd[i]->Mx > x->Scd[i]->Nw /*边有余量*/) {
Q[tl++] = x->Scd[i]->To; //入队
Cm[x->Scd[i]->To - N] = x; //前驱(从 x 来)
NodeFl[x->Scd[i]->To - N] =
min(x->Scd[i]->Mx - x->Scd[i]->Nw /*余量*/,
NodeFl[x - N] /*当前点入量*/); //流入目标点
if (x->Scd[i]->To == T) { //搜到汇点, 本轮结束
return;
}
}
}
}
return;
}
更新残量网络 Bk
顺着之前记录的前驱组成的链往回走, 借助邻接矩阵更新边流量.
void Bk() {
Node *x(T) /*从汇点出发*/, *y(Cm[x - N]) /*往前驱走*/;
long long tmp(NodeFl[T - N]); //这条增广路的流
while (y) { //没走到源点就继续(源点没有前驱)
y->Fst[x - N]->Nw += tmp; //正向边流量增
x->Fst[y - N]->Nw -= tmp; //反向边流量减
x = y; //往前移动至前驱
y = Cm[y - N]; //前驱更新
}
return;
}
Dinic
没什么新东西, 只是在 Edmonds-Karp
的基础上变了变形. 操作都封装在 BFS
和 DFS
里了.
void Dinic() {
while (1) { //找到不能找为止
memset(Q, 0, sizeof(Q));
memset(Dep, 0, sizeof(Dep));
hd = 0;
tl = 1;
Q[hd] = S; //还是初始化
Dep[S - N] = 1; //源点深度为 1
BFS(); // 分层
if (!Dep[T - N]) { // BFS搜不到汇点, 已无增广路
break;
}
Ans += DFS(S,
0x3f3f3f3f3f3f3f3f); //流入源点无限流,
//函数返回找到的所有增广路流的和, 计入答案
}
return;
}
分层 BFS
比 Edmonds-Karp
的 Fnd
简单了一些, 需要注意的是这个 BFS 搜到汇点不返回, 因为在 DFS 中, 为了优化效率, 无论什么深度的点, 都可以直接通往汇点, 所以搜索标记深度比汇点大的点.
void BFS() {
Node *x;
while (hd < tl) {
x = Q[hd++]; //取队首
for (register unsigned int i(1); i <= x->Cntne; i++) {
if (!Dep[x->Scd[i]->To - N] /*无深度 (还没搜到)*/ &&
x->Scd[i]->Nw < x->Scd[i]->Mx /*有余量*/) {
Dep[x->Scd[i]->To - N] = Dep[x - N] + 1; //深度比当前点大 1
Q[tl++] = x->Scd[i]->To; //入队
}
}
}
return;
}
DFS
这是 Dinic 的核心, 摒弃了之前 BFS 最后更新残量网络的做法, 因为 DFS 的回溯时更新时很方便的.
long long DFS(Node *x, long long Cm /*尝试流入当前点的流*/) {
long long tmp, sum(0) /*从本点流出, 最后流入汇点的流*/;
for (register unsigned int i(1); i <= x->Cntne; i++) {
if (x->Scd[i]->To == T) { //汇点
tmp = min(x->Scd[i]->Mx - x->Scd[i]->Nw, Cm); //流入汇点
sum += tmp; //统计入当前点流出流
x->Scd[i]->Nw += tmp;
T->Fst[x - N]->Nw -= tmp; //直接更新残量网络
continue;
}
if (x->Scd[i]->Mx > x->Scd[i]->Nw &&
Dep[x->Scd[i]->To - N] ==
Dep[x - N] + 1) { //通往下一层的点的有余量的边
if (Cm == 0) { //可流入的流已留光
return sum;
}
tmp = min(x->Scd[i]->Mx - x->Scd[i]->Nw, Cm); //可流入目标点的流量
if (tmp = DFS(x->Scd[i]->To, tmp)) { //实际流入汇点的流量
Cm -= tmp; //更新当前点流量
x->Scd[i]->Nw += tmp;
x->Scd[i]->To->Fst[x - N]->Nw -= tmp; //更新残量网络
sum += tmp; //统计答案
}
}
}
return sum;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
2020-02-02 洛谷网校:树形问题