网络流(最大流)

网络流(最大流)

1.1基本概念

在一个有向图上选择一个源点,一个汇点,每一条边上都有一个流量上限(以下称为容量),即经过这条边的流量不能超过这个上界,同时,除源点和汇点外,所有点的入流和出流都相等,而源点只有流出的流,汇点只有汇入的流。这样的图叫做网络流

1.2.1 条件限制
  • 容量限制:对于任意u,v,都有f(u,v) <= c(u,v)。即任意时刻流量都不超过容量;
  • 流守恒性:除了源点与汇点之外,其余任意一点,都满足流入该点总量 = 流出该点总量,节点自身不会产生或者消耗流量,只是中转媒介;
  • 反对称性:对于任意的u,v ,一定有f(u,v) = -f(v,u) 。 从u到v流了 l 流量就相当于从v到u流了-l 的流量;

1.2.2 可行流的流量

指从源点流出的流量 - 流入源点的流量

1.2.3 最大流

对于网络 ( G = (V, E) ),给每条边指定流量,得到合适的流 ( f ),使得 ( f ) 的流量尽可能大。此时我们称 ( f ) 是 ( G ) 的最大流。

1.2.4 增广路径

如果残留网中一直选择边权大于0的边走可以从源点走到汇点那么就存在一条增广路

残量网络
  • 残量网络的可行流 + 原网络的可行流 = 原问题的另一个可行流

最小割问题

对于网络 ( G = (V, E) ),找到合适的 ( s-t ) 割 ({S, T}),使得 ({S, T}) 的总容量尽可能小。此时我们称 ({S, T}) 的总容量是 ( G ) 的最小割。

最小费用最大流问题

在网络 ( G = (V, E) ) 上,对每条边给定一个权值 ( w(u, v) ),称为费用(cost),含义是单位流量通过 ((u, v)) 所花费的代价。对于 ( G ) 所有可能的最大流,我们称其中总费用最小的一者为最小费用最大流。

最大流最小割定理

对于任意割 [S,T],|f| <= c(S,T)

以下三个条件等价:

  1. 流 f 是最大流
  2. 流 f 的残留网络中不存在增广路
  3. 存在某个割 [S,T],使得 |f| = c(S,T)

求解算法

Edmonds-Karp (EK) 算法

一、算法概述

核心思想:采用BFS寻找增广路径,保证找到最短增广路

主要特点

  • 属于增广路算法家族
  • 时间复杂度::O(VE²)
  • 适合稀疏图的中小规模问题

二、算法原理

2.1 残量网络定义

对于流网络 G=(V,E) 和流 f,残量网络 G_f 满足:

\[c_f(u,v) = \begin{cases} c(u,v) - f(u,v) & \text{if } (u,v) \in E \\ f(v,u) & \text{if } (v,u) \in E \\ 0 & \text{otherwise} \end{cases} \]

2.2 关键操作

  1. BFS寻路:每次找最短的s→t路径

  2. 瓶颈值计算

    \[\ b = \min\{c_f(u,v) \mid (u,v)\in p\} \]

  3. 增广操作:沿路径增减流量

Dinic 算法

一、详解

Dinic算法总是寻找最短的增广路,并沿着它增广。因为最短增广路的长度在增广过程中始终不会变短,所以无需每次都通过深度预先搜索来寻找最短增广路,我们可以先进行一次宽度优先搜索,然后考虑由近距离顶点指向远距离顶点的边所组成的分层图,在上面进行深度优先搜索寻找最短增广路。如果在分层图上找不到新的增广路了,则说明最短增广路的长度确实变长了,或不存在增广路了,于是重新通过宽度优先搜索构造新的分层图。此外,还可以对这个算法进行优化。
当前弧优化:在每次对分层图进行深度优先搜索寻找增广路时,避免对一条没有用的边进行多次检查。

优势:比EK更高效的最大流算法,时间复杂度 O(V²E)

二、核心思想

双重优化

  1. 分层图:通过BFS建立层次结构
  2. **阻塞流:在层次图上用DFS找增广路

ISAP算法

它是Dinic算法的改进版本

内容

  1. 层次图:与Dinic算法类似,ISAP也使用层次图的概念,即从源点出发按照BFS分层。
  2. 反向BFS:ISAP的一个关键改进是从汇点开始进行反向BFS来初始化距离标号,而不是每次阻塞流后重新BFS。
  3. 间隙优化:当某个距离值没有节点时,可以确定已经找到最大流。
  4. 时间复杂度:O(V²E),与Dinic算法相同,但实际运行效率通常更高
  5. 空间复杂度:O(V+E)

HLLP(预流推进)

核心思想:通过局部操作(推进+重标记)而非全局增广路来求解最大流

内容
  1. 预流(Preflow):允许节点的流入量暂时大于流出量(即存在"超额流")。
  2. 高度标号:为每个节点分配高度,流只能从高节点推向低节点。
  3. 最高标号优先:总是处理当前高度最高的超额节点,减少无效操作。
  4. **时间复杂度 O(V²√E)

初始化

  • 高度标号
    • 源点高度设为 h[s] = V(节点总数),汇点 h[t] = 0
    • 其他节点 h[u] = 0
  • 预流
    • 从源点 s 向所有相邻节点推流,使其达到饱和(即边容量被完全利用)。
    • 此时源点可能仍有超额流,其他节点开始积累超额流。

2. 推进(Push)操作

对当前最高标号的超额节点 u,尝试将超额流推向邻居 v

  • 条件
    • h[u] = h[v] + 1(满足高度约束)。
    • (u,v) 有剩余容量 c(u,v) > 0
  • 操作
    • 推送量 Δ = min(d[u], c(u,v))
    • 更新残余网络:c(u,v) -= Δc(v,u) += Δ
    • 更新超额流:d[u] -= Δd[v] += Δ

3. 重标号(Relabel)操作

当节点 u 有超额流但无法推送时:

  • 将其高度提升为 h[u] = min{h[v]} + 1vu 的邻居且边有剩余容量)。
  • 保证后续能继续推流。

4. 最高标号优先策略

  • 使用一个桶(Bucket)或优先队列维护当前所有超额节点,按高度从高到低处理。
  • 避免低标号节点的无效操作,加速收敛。

5. 终止条件

  • 当除源点 s 和汇点 t 外,所有节点的超额流为 0
  • 此时汇点 t 的流入量即为最大流。

Template

EK算法

template <typename T>
struct EK_ {
    const int n;
    const T inf = std::numeric_limits<T>::max();
    
    struct Edge {
        int to;
        T w;
        Edge(int to, T w) : to(to), w(w) {}
    };
    
    std::vector<Edge> ver;
    std::vector<std::vector<int>> h;
    std::vector<int> pre;
    
    EK_(int n) : n(n + 1), h(n + 1) {}  
    
    void add(int u, int v, T c) {
        h[u].push_back((int)ver.size());
        ver.emplace_back(v, c);
        h[v].push_back((int)ver.size());
        ver.emplace_back(u, 0);
    }
    
    bool bfs(int s, int t) {
        pre.assign(n, -1);
        std::queue<int> q;
        q.push(s);
        pre[s] = 0;  
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            
            for (int id : h[u]) {
                int v = ver[id].to;
                if (pre[v] == -1 && ver[id].w > 0) {
                    pre[v] = id; 
                    if (v == t) return true;
                    q.push(v);
                }
            }
        }
        return false;
    }
    
    T work(int s, int t) {
        T max_flow = 0;
        
        while (bfs(s, t)) {
            T min_flow = inf;
            for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
                min_flow = std::min(min_flow, ver[pre[v]].w);
            }
            for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
                ver[pre[v]].w -= min_flow;
                ver[pre[v] ^ 1].w += min_flow;
            }
            
            max_flow += min_flow;
        }
        
        return max_flow;
    }
};

using EK = EK_<i64>;

Dinic

template <typename T> struct Flow_ {
    const int n;
    const T inf = numeric_limits<T>::max();
    struct Edge {
        int to;
        T w;
        Edge(int to, T w) : to(to), w(w) {}
    };
    vector<Edge> ver;
    vector<vector<int>> h;
    vector<int> cur, d;
    
    Flow_(int n) : n(n + 1), h(n + 1) {}
    void add(int u, int v, T c) {
        h[u].push_back(ver.size());
        ver.emplace_back(v, c);
        h[v].push_back(ver.size());
        ver.emplace_back(u, 0);
    }
    bool bfs(int s, int t) {
        d.assign(n, -1);
        d[s] = 0;
        queue<int> q;
        q.push(s);
        while (!q.empty()) {
            auto x = q.front();
            q.pop();
            for (auto it : h[x]) {
                auto [y, w] = ver[it];
                if (w && d[y] == -1) {
                    d[y] = d[x] + 1;
                    if (y == t) return true;
                    q.push(y);
                }
            }
        }
        return false;
    }
    T dfs(int u, int t, T f) {
        if (u == t) return f;
        auto r = f;
        for (int &i = cur[u]; i < h[u].size(); i++) {
            auto j = h[u][i];
            auto &[v, c] = ver[j];
            auto &[u, rc] = ver[j ^ 1];
            if (c && d[v] == d[u] + 1) {
                auto a = dfs(v, t, std::min(r, c));
                c -= a;
                rc += a;
                r -= a;
                if (!r) return f;
            }
        }
        return f - r;
    }
    T work(int s, int t) {
        T ans = 0;
        while (bfs(s, t)) {
            cur.assign(n, 0);
            ans += dfs(s, t, inf);
        }
        return ans;
    }
};
using Flow = Flow_<int>;

ISAP

template <typename T>
struct ISAP_ {
  const int n;
  const T inf = std::numeric_limits<T>::max();

  struct Edge {
    int to;
    T w;
    Edge(int to, T w) : to(to), w(w) {}
  };
  std::vector<Edge> ver;           
  std::vector<std::vector<int>> h; 
  std::vector<int> cur;            
  std::vector<int> d;              
  std::vector<int> pre;            
  std::vector<int> cnt;            

  ISAP_(int n) : n(n + 1), h(n + 1) {}

  void add(int u, int v, T c) {
    h[u].push_back((int)ver.size());
    ver.emplace_back(v, c);
    h[v].push_back((int)ver.size());
    ver.emplace_back(u, 0);
  }
  bool bfs(int s, int t) {
    d.assign(n, -1);
    std::queue<int> q;
    d[t] = 0;
    q.push(t);
    while (!q.empty()) {
      int x = q.front();
      q.pop();
      for (int id : h[x]) {
        int y = ver[id].to;
        if (d[y] == -1) {
          d[y] = d[x] + 1;
          q.push(y);
        }
      }
    }
    for (int i = 1; i < n; ++i)
      if (d[i] == -1) d[i] = n;
    cnt.assign(n + 1, 0);
    for (int i = 1; i < n; ++i)
      if (d[i] <= n) ++cnt[d[i]];
    return d[s] < n;
  }
  T dfs(int, int, T) { return 0; }
  T work(int s, int t) {
    if (!bfs(s, t)) return 0; 
    cur.assign(n, 0);
    pre.assign(n, -1);

    T max_flow = 0;
    int x = s;

    while (d[s] < n) {
      bool advanced = false;
      for (int &i = cur[x]; i < (int)h[x].size(); ++i) {
        int id = h[x][i];
        int v = ver[id].to;
        if (ver[id].w > 0 && d[x] == d[v] + 1) {
          pre[v] = id;
          x = v;
          advanced = true;
          break;
        }
      }

      if (!advanced) {
        int min_d = n - 1;
        for (int id : h[x]) {
          if (ver[id].w > 0) {
            min_d = std::min(min_d, d[ver[id].to]);
          }
        }
        int old = d[x];
        if (--cnt[old] == 0) break;  // gap 优化
        d[x] = min_d + 1;
        ++cnt[d[x]];
        cur[x] = 0;
        if (x != s) x = ver[pre[x] ^ 1].to;  // 退回到前驱点
      }

      if (x == t) {
        // 增广
        T aug = inf;
        for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
          aug = std::min(aug, ver[pre[v]].w);
        }
        for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
          int id = pre[v];
          ver[id].w -= aug;
          ver[id ^ 1].w += aug;
        }
        max_flow += aug;
        x = s;
      }
    }
    return max_flow;
  }
};

using ISAP = ISAP_<i64>;

HLLP

template <typename T> struct PushRelabel {
    const int inf = 0x3f3f3f3f;
    const T INF = 0x3f3f3f3f3f3f3f3f;
    struct Edge {
        int to, cap, flow, anti;
        Edge(int v = 0, int w = 0, int id = 0) : to(v), cap(w), flow(0), anti(id) {}
    };
    vector<vector<Edge>> e; 
    vector<vector<int>> gap;
    vector<T> ex; // 超额流
    vector<bool> ingap;
    vector<int> h;
    int n, gobalcnt, maxH = 0;
    T maxflow = 0;

    PushRelabel(int n) : n(n), e(n + 1), ex(n + 1), gap(n + 1) {}
    void addedge(int u, int v, int w) {
        e[u].push_back({v, w, (int)e[v].size()});
        e[v].push_back({u, 0, (int)e[u].size() - 1});
    }
    void PushEdge(int u, Edge &edge) {
        int v = edge.to, d = min(ex[u], 1LL * edge.cap - edge.flow);
        ex[u] -= d;
        ex[v] += d;
        edge.flow += d;
        e[v][edge.anti].flow -= d;
        if (h[v] != inf && d > 0 && ex[v] == d && !ingap[v]) {
            ++gobalcnt;
            gap[h[v]].push_back(v);
            ingap[v] = 1;
        }
    }
    void PushPoint(int u) {
        for (auto k = e[u].begin(); k != e[u].end(); k++) {
            if (h[k->to] + 1 == h[u] && k->cap > k->flow) {
                PushEdge(u, *k);
                if (!ex[u]) break;
            }
        }
        if (!ex[u]) return;
        if (gap[h[u]].empty()) {
            for (int i = h[u] + 1; i <= min(maxH, n); i++) {
                for (auto v : gap[i]) {
                    ingap[v] = 0;
                }
                gap[i].clear();
            }
        }
        h[u] = inf;
        for (auto [to, cap, flow, anti] : e[u]) {
            if (cap > flow) {
                h[u] = min(h[u], h[to] + 1);
            }
        }
        if (h[u] >= n) return;
        maxH = max(maxH, h[u]);
        if (!ingap[u]) {
            gap[h[u]].push_back(u);
            ingap[u] = 1;
        }
    }
    void init(int t, bool f = 1) {
        ingap.assign(n + 1, 0);
        for (int i = 1; i <= maxH; i++) {
            gap[i].clear();
        }
        gobalcnt = 0, maxH = 0;
        queue<int> q;
        h.assign(n + 1, inf);
        h[t] = 0, q.push(t);
        while (q.size()) {
            int u = q.front();
            q.pop(), maxH = h[u];
            for (auto &[v, cap, flow, anti] : e[u]) {
                if (h[v] == inf && e[v][anti].cap > e[v][anti].flow) {
                    h[v] = h[u] + 1;
                    q.push(v);
                    if (f) {
                        gap[h[v]].push_back(v);
                        ingap[v] = 1;
                    }
                }
            }
        }
    }
    T work(int s, int t) {
        init(t, 0);
        if (h[s] == inf) return maxflow;
        h[s] = n;
        ex[s] = INF;
        ex[t] = -INF;
        for (auto k = e[s].begin(); k != e[s].end(); k++) {
            PushEdge(s, *k);
        }
        while (maxH > 0) {
            if (gap[maxH].empty()) {
                maxH--;
                continue;
            }
            int u = gap[maxH].back();
            gap[maxH].pop_back();
            ingap[u] = 0;
            PushPoint(u);
            if (gobalcnt >= 10 * n) {
                init(t);
            }
        }
        ex[s] -= INF;
        ex[t] += INF;
        return maxflow = ex[t];
    }
};

模板题

P3376 【模板】网络最大流
xxx

练习题

网络流24题

网络流从入门到入土

posted @ 2025-08-08 14:14  orzzzzz  阅读(55)  评论(1)    收藏  举报