P3376 【模板】网络最大流

题意

Luogu P3376

求网络最大流.

首先介绍一下网络流的基本概念. 一个带权有向图, 边权称为容量. 其中一个只有出度的点, 源点, 一个只有入度的点, 汇点.

流从源点源源不断地产生, 在汇点消失. 边的容量就是能它流过的最大流, 实际流过的流成为边的流量. 源点流出的最大的流一定和汇点消失的最大的流相等, 我们把这个最大流称为这个网络的流.

残量网络

有流流过网络时, 边的剩余剩余流量构成的网络, 称残量网络.

一条边本没有反向容量, 但是由于有流正向流过时, 相当于给这条边减少了反向流量. 反向流量为负数, 反向容量为 0, 所以就出现了反向余量.

为了记录反向流量, 我们给每一条边另开一条反向的容量为 0 的边, 以完整存储残量网络.

增广路

残量网络里, 一条从源点通往汇点的路径, 称为增广路, 即可以增加当前网络流量的路径. 只要找到一条增广路, 就可以更新残量网络.

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 的基础上变了变形. 操作都封装在 BFSDFS 里了.

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-KarpFnd 简单了一些, 需要注意的是这个 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;
}
posted @   Wild_Donkey  阅读(90)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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 洛谷网校:树形问题
点击右上角即可分享
微信分享提示