Skip to content

global_min_cut.hpp

SECTIONGraph INCLUDEnoya/graph/global_min_cut.hpp

Stoer–Wagner 全局最小割,返回割权和确定的割侧;融合候选扫描,避免顶点组复制,遇到零割提前结束。

Complexity: Time: \(O(V^{3})\). Space: \(O(V^{2})\), including the matrix copy. Other workspace is \(O(V)\). A zero phase cut terminates the search early.

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(V^3). Space: O(V^2), including the matrix copy.
/// Other workspace is O(V). A zero phase cut terminates the search early.

#include <algorithm>
#include <cassert>
#include <numeric>
#include <utility>
#include <vector>

namespace noya {

/// @brief Stoer-Wagner global minimum cut, returning its weight and a sorted
/// side. Require a square symmetric nonnegative matrix with zero diagonal;
/// all weights and intermediate sums must be finite and representable in T.
/// The matrix is passed by value; an lvalue input is unchanged. Preserve the
/// original smallest-vertex phase ties and first strictly best cut. For n=0
/// return {0,{}}; for n=1 return {0,{0}}. No recursion or numeric sentinels.
template <class T>
std::pair<T, std::vector<int>> global_min_cut(std::vector<std::vector<T>> w) {
  int n = int(w.size());
  for (const auto &row : w) {
    assert(int(row.size()) == n);
  }
  for (int i = 0; i < n; ++i) {
    assert(w[i][i] == T{});
    for (int j = 0; j < n; ++j) {
      assert(w[i][j] == w[j][i] && !(w[i][j] < T{}));
    }
  }
  if (n <= 1) {
    return {T{}, n == 0 ? std::vector<int>{} : std::vector<int>{0}};
  }

  std::vector<int> active(n), tail(n), next(n, -1), todo;
  std::iota(active.begin(), active.end(), 0);
  tail = active;
  std::vector<T> con;
  T best{};
  int begin = -1, end = -1;
  while (active.size() > 1) {
    int pre = active.front();
    todo.assign(active.begin() + 1, active.end());
    con.assign(todo.size(), T{});
    for (int count = int(todo.size()); count > 0; --count) {
      const auto &row = w[pre];
      con[0] += row[todo[0]];
      T maximum = con[0];
      int at = 0;
      // Fuse connection-weight updates and selection. Keeping todo sorted
      // preserves the smallest original vertex on ties without another test.
      for (int i = 1; i < count; ++i) {
        con[i] += row[todo[i]];
        if (maximum < con[i]) {
          maximum = con[i];
          at = i;
        }
      }
      int sel = todo[at];
      if (count == 1) {
        // The first cut is valid even when its weight equals T's maximum.
        if (begin == -1 || maximum < best) {
          best = maximum;
          begin = sel;
          end = tail[sel];
        }
        if (!(T{} < best)) break;
        for (int u : active) {
          if (u != pre && u != sel) {
            w[pre][u] += w[sel][u];
            w[u][pre] = w[pre][u];
          }
        }
        // Each merge fills one tail's null link. Saving both endpoints keeps
        // a previous best group intact even when its old tail is extended.
        next[tail[pre]] = sel;
        tail[pre] = tail[sel];
        active.erase(std::find(active.begin(), active.end(), sel));
        break;
      }
      todo.erase(todo.begin() + at);
      con.erase(con.begin() + at);
      pre = sel;
    }
    if (!(T{} < best)) break;
  }
  std::vector<int> side;
  for (int u = begin;; u = next[u]) {
    side.push_back(u);
    if (u == end) break;
  }
  std::sort(side.begin(), side.end());
  return {best, std::move(side)};
}

} // namespace noya

 // NOYA_GLOBAL_MIN_CUT_HPP
#ifndef NOYA_GLOBAL_MIN_CUT_HPP
#define NOYA_GLOBAL_MIN_CUT_HPP 1

/// @complexity Time: O(V^3). Space: O(V^2), including the matrix copy.
/// Other workspace is O(V). A zero phase cut terminates the search early.

#include <algorithm>
#include <cassert>
#include <numeric>
#include <utility>
#include <vector>

namespace noya {

/// @brief Stoer-Wagner global minimum cut, returning its weight and a sorted
/// side. Require a square symmetric nonnegative matrix with zero diagonal;
/// all weights and intermediate sums must be finite and representable in T.
/// The matrix is passed by value; an lvalue input is unchanged. Preserve the
/// original smallest-vertex phase ties and first strictly best cut. For n=0
/// return {0,{}}; for n=1 return {0,{0}}. No recursion or numeric sentinels.
template <class T>
std::pair<T, std::vector<int>> global_min_cut(std::vector<std::vector<T>> w) {
  int n = int(w.size());
  for (const auto &row : w) {
    assert(int(row.size()) == n);
  }
  for (int i = 0; i < n; ++i) {
    assert(w[i][i] == T{});
    for (int j = 0; j < n; ++j) {
      assert(w[i][j] == w[j][i] && !(w[i][j] < T{}));
    }
  }
  if (n <= 1) {
    return {T{}, n == 0 ? std::vector<int>{} : std::vector<int>{0}};
  }

  std::vector<int> active(n), tail(n), next(n, -1), todo;
  std::iota(active.begin(), active.end(), 0);
  tail = active;
  std::vector<T> con;
  T best{};
  int begin = -1, end = -1;
  while (active.size() > 1) {
    int pre = active.front();
    todo.assign(active.begin() + 1, active.end());
    con.assign(todo.size(), T{});
    for (int count = int(todo.size()); count > 0; --count) {
      const auto &row = w[pre];
      con[0] += row[todo[0]];
      T maximum = con[0];
      int at = 0;
      // Fuse connection-weight updates and selection. Keeping todo sorted
      // preserves the smallest original vertex on ties without another test.
      for (int i = 1; i < count; ++i) {
        con[i] += row[todo[i]];
        if (maximum < con[i]) {
          maximum = con[i];
          at = i;
        }
      }
      int sel = todo[at];
      if (count == 1) {
        // The first cut is valid even when its weight equals T's maximum.
        if (begin == -1 || maximum < best) {
          best = maximum;
          begin = sel;
          end = tail[sel];
        }
        if (!(T{} < best)) break;
        for (int u : active) {
          if (u != pre && u != sel) {
            w[pre][u] += w[sel][u];
            w[u][pre] = w[pre][u];
          }
        }
        // Each merge fills one tail's null link. Saving both endpoints keeps
        // a previous best group intact even when its old tail is extended.
        next[tail[pre]] = sel;
        tail[pre] = tail[sel];
        active.erase(std::find(active.begin(), active.end(), sel));
        break;
      }
      todo.erase(todo.begin() + at);
      con.erase(con.begin() + at);
      pre = sel;
    }
    if (!(T{} < best)) break;
  }
  std::vector<int> side;
  for (int u = begin;; u = next[u]) {
    side.push_back(u);
    if (u == end) break;
  }
  std::sort(side.begin(), side.end());
  return {best, std::move(side)};
}

} // namespace noya

#endif // NOYA_GLOBAL_MIN_CUT_HPP
/// @complexity Time: O(V^3). Space: O(V^2), including the matrix copy.
/// Other workspace is O(V). A zero phase cut terminates the search early.

#include <algorithm>
#include <cassert>
#include <numeric>
#include <utility>
#include <vector>

namespace noya {

/// @brief Stoer-Wagner global minimum cut, returning its weight and a sorted
/// side. Require a square symmetric nonnegative matrix with zero diagonal;
/// all weights and intermediate sums must be finite and representable in T.
/// The matrix is passed by value; an lvalue input is unchanged. Preserve the
/// original smallest-vertex phase ties and first strictly best cut. For n=0
/// return {0,{}}; for n=1 return {0,{0}}. No recursion or numeric sentinels.
template <class T>
std::pair<T, std::vector<int>> global_min_cut(std::vector<std::vector<T>> w) {
  int n = int(w.size());
  for (const auto &row : w) {
    assert(int(row.size()) == n);
  }
  for (int i = 0; i < n; ++i) {
    assert(w[i][i] == T{});
    for (int j = 0; j < n; ++j) {
      assert(w[i][j] == w[j][i] && !(w[i][j] < T{}));
    }
  }
  if (n <= 1) {
    return {T{}, n == 0 ? std::vector<int>{} : std::vector<int>{0}};
  }

  std::vector<int> active(n), tail(n), next(n, -1), todo;
  std::iota(active.begin(), active.end(), 0);
  tail = active;
  std::vector<T> con;
  T best{};
  int begin = -1, end = -1;
  while (active.size() > 1) {
    int pre = active.front();
    todo.assign(active.begin() + 1, active.end());
    con.assign(todo.size(), T{});
    for (int count = int(todo.size()); count > 0; --count) {
      const auto &row = w[pre];
      con[0] += row[todo[0]];
      T maximum = con[0];
      int at = 0;
      // Fuse connection-weight updates and selection. Keeping todo sorted
      // preserves the smallest original vertex on ties without another test.
      for (int i = 1; i < count; ++i) {
        con[i] += row[todo[i]];
        if (maximum < con[i]) {
          maximum = con[i];
          at = i;
        }
      }
      int sel = todo[at];
      if (count == 1) {
        // The first cut is valid even when its weight equals T's maximum.
        if (begin == -1 || maximum < best) {
          best = maximum;
          begin = sel;
          end = tail[sel];
        }
        if (!(T{} < best)) break;
        for (int u : active) {
          if (u != pre && u != sel) {
            w[pre][u] += w[sel][u];
            w[u][pre] = w[pre][u];
          }
        }
        // Each merge fills one tail's null link. Saving both endpoints keeps
        // a previous best group intact even when its old tail is extended.
        next[tail[pre]] = sel;
        tail[pre] = tail[sel];
        active.erase(std::find(active.begin(), active.end(), sel));
        break;
      }
      todo.erase(todo.begin() + at);
      con.erase(con.begin() + at);
      pre = sel;
    }
    if (!(T{} < best)) break;
  }
  std::vector<int> side;
  for (int u = begin;; u = next[u]) {
    side.push_back(u);
    if (u == end) break;
  }
  std::sort(side.begin(), side.end());
  return {best, std::move(side)};
}

} // namespace noya

 // NOYA_GLOBAL_MIN_CUT_HPP