網路流(最大流,最小割)

yabnto發表於2024-08-22

網路流(最大流)

給定一個網路,有源點和匯點,現在要往源點灌水,問每單位時間可以從匯點出多少水,並且每一條邊有限流。

P3376【模板】網路最大流

一個幾乎沒用的東西:FF

思路

我們很顯然會有個思路,就是每次 \(DFS\) 搜尋,找到一條路徑,並且是可以增廣的(限流還沒達到),那麼就增廣它,可是如果遇到一條路徑走會滿流,但分開走就能流出最大流的情況,所以我們需要一個反悔的機會,可以將剩餘可以流的水量建一條正向邊形成殘餘網路,已經流的建一條反向邊,於是就有了一個反悔的機會。

EK

思路

\(DFS\) 改成 \(BFS\) 即可,這樣可以保證每次找到的路徑是最短的,但是我們在增廣的時候需要把路徑弄出來,所以我們用一個 \(pre\) 陣列來記錄上一個結點,這樣就可以記錄路徑了。

code

#include <iostream>
#include <queue>

using namespace std;
using ll = long long;

const int MaxN = 210, MaxM = 2 * 5010;

struct Edge {
  ll v, w, nxt;
} e[MaxM];

ll h[MaxN], ans;
int n, m, s, t, cnt = 1;
bool vis[MaxN];
queue<int> q;
pair<int, int> p[MaxN];

ll G(int x, ll minx = 1e18) {
  if (x == s) {
    return minx;
  }
  ll res = G(p[x].first, min(minx, e[p[x].second].w));
  e[p[x].second ^ 1].w += res;
  e[p[x].second].w -= res;
  return res;
}

void Record(int u, int i) {
  if (vis[e[i].v] || !e[i].w) {
    return;
  }
  vis[e[i].v] = 1;
  p[e[i].v] = {u, i};
  q.push(e[i].v);
}

ll BFS() {
  for (int i = 1; i <= n; i++) {
    vis[i] = 0, p[i] = {0, 0};
  }
  queue<int>().swap(q);
  for (q.push(s), vis[s] = 1; !q.empty(); q.pop()) {
    int u = q.front();    
    if (u == t) {
      return G(t);
    }    
    for (int i = h[u]; ~i; i = e[i].nxt) {
      Record(u, i);
    }
  }
  return -1;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m >> s >> t;
  fill(h + 1, h + n + 1, -1);
  for (ll i = 1, u, v, w; i <= m; i++) {
    cin >> u >> v >> w;
    e[++cnt] = {v, w, h[u]}, h[u] = cnt;     
    e[++cnt] = {u, 0, h[v]}, h[v] = cnt;
  }
  for (ll res; ~(res = BFS()); ans += res) {
  }
  cout << ans << '\n';
  return 0;
}

時間複雜度:\(O(nm^2)\)

Dinic

每次對殘餘網路進行分層,將非可是分層的邊(樹上就是橫向邊)刪了,然後找當前圖的最大流,及找到增廣路徑,然後增廣,將當前圖的最大流併入最終的最大流。

最佳化

  1. \(DFS\) 途中,每次將當前邊,設為當前點第一條邊(注意最後你要還原),因為當處理完這條邊後,再處理的話,必然會再一條被增廣的路徑上被卡死。
  2. \(DFS\) 過程中,如果處理完一條邊的答案為 \(0\),那我們標記一下,返回過程中就不走這了,同樣的要還原。

code

#include <iostream>
#include <queue>

using namespace std;
using ll = long long;

const int MaxN = 210, MaxM = 2 * 5010;

struct IF {
  struct Edge {
    ll v, w, nxt;
  } e[MaxM];

  ll dis[MaxN], h[MaxN], th[MaxN], ans, n, s, t, cnt;
  bool vis[MaxN];
  queue<int> q;

  IF() {
    n = s = t = ans = 0, cnt = 1;
    fill(h, h + MaxN, -1);
    fill(dis, dis + MaxN, 0);
  }

  void Record(int u, int i) {
    if (vis[e[i].v] || !e[i].w) {
      return;
    }
    vis[e[i].v] = 1;
    dis[e[i].v] = dis[u] + 1;
    q.push(e[i].v);
  }

  bool BFS() {
    for (int i = 1; i <= n; i++) {
      vis[i] = 0, th[i] = h[i];
    }
    queue<int>().swap(q);
    for (q.push(s), vis[s] = 1, dis[s] = 1; !q.empty(); q.pop()) {
      int u = q.front();
      if (u == t) {
        continue;
      }
      for (int i = h[u]; ~i; i = e[i].nxt) {
        Record(u, i);
      }
    }
    return vis[t];
  }

  ll DFS(int x, ll f) {
    if (x == t || !f) {
      return f;
    }
    ll res = 0;
    for (int i = th[x]; ~i && f - res > 0; i = e[i].nxt) {
      th[x] = i;
      if (e[i].w && dis[x] + 1 == dis[e[i].v]) {
        ll tmp = DFS(e[i].v, min(f - res, e[i].w));
        if (!tmp) {
          dis[e[i].v] = 1e18;
        }
        res += tmp;
        e[i].w -= tmp;
        e[i ^ 1].w += tmp;
      }
    }
    return res;
  }

  void insert(int u, int v, ll w) {
    e[++cnt] = {v, w, h[u]}, h[u] = cnt;
    e[++cnt] = {u, 0, h[v]}, h[v] = cnt;
  }

  ll Solov() {
    for (; BFS(); ans += DFS(s, 1e18)) {
    }
    return ans;
  }
};

int n, m, s, t;

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m >> s >> t;
  IF ans;
  ans.n = n, ans.s = s, ans.t = t;
  for (ll i = 1, u, v, w; i <= m; i++) {
    cin >> u >> v >> w;
    ans.insert(u, v, w);
  }
  cout << ans.Solov() << '\n';
  return 0;
}

時間複雜度

最差:\(O(n^2m)\) 若為二分圖,可以到 \(O(m\sqrt n)\)

最小割

最小割等於最大流

相關文章