Skip to content

dynamic_star_min_cut.hpp

SECTIONGraph INCLUDEnoya/dynamic_star_min_cut.hpp

维护星形端点权变化下的最小割值;适合图结构固定、多个终端容量动态修改的割问题。

Complexity: Time: O(n(n+m) log n + mn) preprocessing and O(log^2 n) per update. Space: O(n+m).

AC 记录:global_minimum_cut_of_dynamic_star_augmented_graph

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(n(n+m) log n + mn) preprocessing and
/// O(log^2 n) per update. Space: O(n+m).

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

namespace dynamic_star_min_cut_internal {

using i64 = std::int64_t;
static constexpr i64 inf = i64(1) << 60;

class range_add_min_tree {
  int siz = 1;
  std::vector<i64> mn, lz;

  void apply(int nd, i64 ad1) {
    mn[nd] += ad1;
    lz[nd] += ad1;
  }

  void push(int nd) {
    apply(2 * nd + 1, lz[nd]);
    apply(2 * nd + 2, lz[nd]);
    lz[nd] = 0;
  }

  void add(int ql, int qr, int nd, int l, int r, i64 ad1) {
    if (r <= ql || qr <= l) {
      return;
    }
    if (ql <= l && r <= qr) {
      apply(nd, ad1);
      return;
    }
    push(nd);
    int mid = (l + r) / 2;
    add(ql, qr, 2 * nd + 1, l, mid, ad1);
    add(ql, qr, 2 * nd + 2, mid, r, ad1);
    mn[nd] = std::min(mn[2 * nd + 1], mn[2 * nd + 2]);
  }

public:
  explicit range_add_min_tree(int n) {
    while (siz < n) {
      siz *= 2;
    }
    mn.assign(2 * siz - 1, inf);
    lz.assign(2 * siz - 1, 0);
  }

  void add(int l, int r, i64 ad1) { add(l, r, 0, 0, siz, ad1); }

  i64 get(int i) const {
    int nd = i + siz - 1;
    i64 res = mn[nd];
    while (nd > 0) {
      nd = (nd - 1) / 2;
      res += lz[nd];
    }
    return res;
  }

  void set(int i, i64 val) { add(i, i + 1, val - get(i)); }

  i64 all_min() const { return mn[0]; }
};

struct weighted_neighbor {
  int u;
  i64 w;
};

/// Build the pendant-pair contraction tree.  Each merge creates the parent of
/// its two contracted clusters, so original vertices remain the leaves.
inline std::vector<std::vector<int>>
build_contraction_tree(std::vector<std::vector<weighted_neighbor>> g) {
  int n = int(g.size());
  std::vector<std::vector<int>> tr(2 * n - 1);
  std::vector<bool> act(2 * n - 1, true);
  for (int ph = 0; ph < n - 1; ph++) {
    g.push_back({});
    std::vector<int> ord(n - ph);
    std::vector<bool> vis(n + ph);
    std::vector<i64> es(n + ph);
    using heap_entry = std::pair<i64, int>;
    std::priority_queue<heap_entry> q;
    for (int u = 0; u < n + ph; u++) {
      if (!act[u]) {
        continue;
      }
      for (auto e : g[u]) {
        es[u] += e.w;
      }
      q.push({-es[u], u});
    }

    for (int pos = 0; pos < n - ph; pos++) {
      while (true) {
        auto [nc, u] = q.top();
        q.pop();
        if (vis[u]) {
          continue;
        }
        vis[u] = true;
        ord[pos] = u;
        for (auto e : g[u]) {
          if (!vis[e.u]) {
            es[e.u] -= e.w;
            q.push({-es[e.u], e.u});
          }
        }
        break;
      }
    }

    int a = ord[n - ph - 1];
    int b = ord[n - ph - 2];
    int mer = n + ph;
    g[a].clear();
    g[b].clear();
    act[a] = act[b] = false;
    for (int u = 0; u < mer; u++) {
      if (!act[u]) {
        continue;
      }
      for (weighted_neighbor &e : g[u]) {
        if (e.u == a || e.u == b) {
          e.u = mer;
          g[mer].push_back({u, e.w});
        }
      }
    }
    tr[mer].push_back(a);
    tr[mer].push_back(b);
  }
  return tr;
}

} // namespace dynamic_star_min_cut_internal

/// @brief Maintain the global min-cut after changing edges of an added star.
/// Repeated minimum-adjacency pendant-pair contractions build a binary cluster
/// tree with an optimal cut among its rooted clusters for every nonnegative
/// star weighting.  A cluster's cut is its original-graph boundary plus the
/// star weights of its leaves.  Thus a leaf update adds one value to all its
/// ancestors; heavy-light path updates and a global-min segment tree maintain
/// the best cluster cut.
class dynamic_star_min_cut {
public:
  using i64 = std::int64_t;
  struct edge {
    int a;
    int b;
    i64 w;
  };

private:
  int nv;
  std::vector<i64> wt;
  std::vector<std::vector<int>> tr;
  std::vector<int> fa, dep, son, hd, ord;
  dynamic_star_min_cut_internal::range_add_min_tree mn;

  int build_heavy(int u) {
    int sz = 1;
    int mc = 0;
    for (int v : tr[u]) {
      fa[v] = u;
      dep[v] = dep[u] + 1;
      int csz = build_heavy(v);
      sz += csz;
      if (csz > mc) {
        mc = csz;
        son[u] = v;
      }
    }
    return sz;
  }

  void build_order(int rt) {
    int ptr = 0;
    std::queue<int> q;
    q.push(rt);
    while (!q.empty()) {
      int ch = q.front();
      q.pop();
      for (int u = ch; u != -1; u = son[u]) {
        ord[u] = ptr++;
        hd[u] = ch;
        for (int v : tr[u]) {
          if (v != son[u]) {
            q.push(v);
          }
        }
      }
    }
  }

  i64 add_to_ancestors(int u, i64 ad1) {
    while (u >= 0) {
      mn.add(ord[hd[u]], ord[u] + 1, ad1);
      u = fa[hd[u]];
    }
    return mn.all_min();
  }

public:
  dynamic_star_min_cut(int n, const std::vector<edge> &es1, std::vector<i64> w0)
      : nv(n), wt(std::move(w0)), tr(), fa(2 * n - 1, -1), dep(2 * n - 1),
        son(2 * n - 1, -1), hd(2 * n - 1), ord(2 * n - 1, -1), mn(2 * n - 1) {
    assert(n >= 1 && int(wt.size()) == n);
    std::vector<std::vector<dynamic_star_min_cut_internal::weighted_neighbor>>
        g(n);
    for (auto [a, b, w] : es1) {
      assert(0 <= a && a < n && 0 <= b && b < n);
      assert(w >= 0);
      if (w != 0) {
        g[a].push_back({b, w});
        g[b].push_back({a, w});
      }
    }
    tr = dynamic_star_min_cut_internal::build_contraction_tree(g);
    int rt = 2 * n - 2;
    build_heavy(rt);
    build_order(rt);

    std::vector<i64> bd(2 * n - 1);
    for (auto [a, b, w] : es1) {
      if (w == 0) {
        continue;
      }
      int l = a;
      int r = b;
      while (l != r) {
        if (dep[l] < dep[r]) {
          std::swap(l, r);
        }
        bd[l] += w;
        l = fa[l];
      }
    }
    for (int u = 0; u < 2 * n - 1; u++) {
      mn.set(ord[u], bd[u]);
    }
    for (int u = 0; u < n; u++) {
      assert(wt[u] >= 0);
      add_to_ancestors(u, wt[u]);
    }
  }

  /// Change one star-edge weight and return the new global minimum cut.
  i64 set_star_weight(int u, i64 w) {
    assert(0 <= u && u < nv && w >= 0);
    i64 res = add_to_ancestors(u, w - wt[u]);
    wt[u] = w;
    return res;
  }

  i64 global_min_cut() const { return mn.all_min(); }
};

} // namespace noya
#ifndef NOYA_DYNAMIC_STAR_MIN_CUT_HPP
#define NOYA_DYNAMIC_STAR_MIN_CUT_HPP 1

/// @complexity Time: O(n(n+m) log n + mn) preprocessing and
/// O(log^2 n) per update. Space: O(n+m).

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

namespace dynamic_star_min_cut_internal {

using i64 = std::int64_t;
static constexpr i64 inf = i64(1) << 60;

class range_add_min_tree {
  int siz = 1;
  std::vector<i64> mn, lz;

  void apply(int nd, i64 ad1) {
    mn[nd] += ad1;
    lz[nd] += ad1;
  }

  void push(int nd) {
    apply(2 * nd + 1, lz[nd]);
    apply(2 * nd + 2, lz[nd]);
    lz[nd] = 0;
  }

  void add(int ql, int qr, int nd, int l, int r, i64 ad1) {
    if (r <= ql || qr <= l) {
      return;
    }
    if (ql <= l && r <= qr) {
      apply(nd, ad1);
      return;
    }
    push(nd);
    int mid = (l + r) / 2;
    add(ql, qr, 2 * nd + 1, l, mid, ad1);
    add(ql, qr, 2 * nd + 2, mid, r, ad1);
    mn[nd] = std::min(mn[2 * nd + 1], mn[2 * nd + 2]);
  }

public:
  explicit range_add_min_tree(int n) {
    while (siz < n) {
      siz *= 2;
    }
    mn.assign(2 * siz - 1, inf);
    lz.assign(2 * siz - 1, 0);
  }

  void add(int l, int r, i64 ad1) { add(l, r, 0, 0, siz, ad1); }

  i64 get(int i) const {
    int nd = i + siz - 1;
    i64 res = mn[nd];
    while (nd > 0) {
      nd = (nd - 1) / 2;
      res += lz[nd];
    }
    return res;
  }

  void set(int i, i64 val) { add(i, i + 1, val - get(i)); }

  i64 all_min() const { return mn[0]; }
};

struct weighted_neighbor {
  int u;
  i64 w;
};

/// Build the pendant-pair contraction tree.  Each merge creates the parent of
/// its two contracted clusters, so original vertices remain the leaves.
inline std::vector<std::vector<int>>
build_contraction_tree(std::vector<std::vector<weighted_neighbor>> g) {
  int n = int(g.size());
  std::vector<std::vector<int>> tr(2 * n - 1);
  std::vector<bool> act(2 * n - 1, true);
  for (int ph = 0; ph < n - 1; ph++) {
    g.push_back({});
    std::vector<int> ord(n - ph);
    std::vector<bool> vis(n + ph);
    std::vector<i64> es(n + ph);
    using heap_entry = std::pair<i64, int>;
    std::priority_queue<heap_entry> q;
    for (int u = 0; u < n + ph; u++) {
      if (!act[u]) {
        continue;
      }
      for (auto e : g[u]) {
        es[u] += e.w;
      }
      q.push({-es[u], u});
    }

    for (int pos = 0; pos < n - ph; pos++) {
      while (true) {
        auto [nc, u] = q.top();
        q.pop();
        if (vis[u]) {
          continue;
        }
        vis[u] = true;
        ord[pos] = u;
        for (auto e : g[u]) {
          if (!vis[e.u]) {
            es[e.u] -= e.w;
            q.push({-es[e.u], e.u});
          }
        }
        break;
      }
    }

    int a = ord[n - ph - 1];
    int b = ord[n - ph - 2];
    int mer = n + ph;
    g[a].clear();
    g[b].clear();
    act[a] = act[b] = false;
    for (int u = 0; u < mer; u++) {
      if (!act[u]) {
        continue;
      }
      for (weighted_neighbor &e : g[u]) {
        if (e.u == a || e.u == b) {
          e.u = mer;
          g[mer].push_back({u, e.w});
        }
      }
    }
    tr[mer].push_back(a);
    tr[mer].push_back(b);
  }
  return tr;
}

} // namespace dynamic_star_min_cut_internal

/// @brief Maintain the global min-cut after changing edges of an added star.
/// Repeated minimum-adjacency pendant-pair contractions build a binary cluster
/// tree with an optimal cut among its rooted clusters for every nonnegative
/// star weighting.  A cluster's cut is its original-graph boundary plus the
/// star weights of its leaves.  Thus a leaf update adds one value to all its
/// ancestors; heavy-light path updates and a global-min segment tree maintain
/// the best cluster cut.
class dynamic_star_min_cut {
public:
  using i64 = std::int64_t;
  struct edge {
    int a;
    int b;
    i64 w;
  };

private:
  int nv;
  std::vector<i64> wt;
  std::vector<std::vector<int>> tr;
  std::vector<int> fa, dep, son, hd, ord;
  dynamic_star_min_cut_internal::range_add_min_tree mn;

  int build_heavy(int u) {
    int sz = 1;
    int mc = 0;
    for (int v : tr[u]) {
      fa[v] = u;
      dep[v] = dep[u] + 1;
      int csz = build_heavy(v);
      sz += csz;
      if (csz > mc) {
        mc = csz;
        son[u] = v;
      }
    }
    return sz;
  }

  void build_order(int rt) {
    int ptr = 0;
    std::queue<int> q;
    q.push(rt);
    while (!q.empty()) {
      int ch = q.front();
      q.pop();
      for (int u = ch; u != -1; u = son[u]) {
        ord[u] = ptr++;
        hd[u] = ch;
        for (int v : tr[u]) {
          if (v != son[u]) {
            q.push(v);
          }
        }
      }
    }
  }

  i64 add_to_ancestors(int u, i64 ad1) {
    while (u >= 0) {
      mn.add(ord[hd[u]], ord[u] + 1, ad1);
      u = fa[hd[u]];
    }
    return mn.all_min();
  }

public:
  dynamic_star_min_cut(int n, const std::vector<edge> &es1, std::vector<i64> w0)
      : nv(n), wt(std::move(w0)), tr(), fa(2 * n - 1, -1), dep(2 * n - 1),
        son(2 * n - 1, -1), hd(2 * n - 1), ord(2 * n - 1, -1), mn(2 * n - 1) {
    assert(n >= 1 && int(wt.size()) == n);
    std::vector<std::vector<dynamic_star_min_cut_internal::weighted_neighbor>>
        g(n);
    for (auto [a, b, w] : es1) {
      assert(0 <= a && a < n && 0 <= b && b < n);
      assert(w >= 0);
      if (w != 0) {
        g[a].push_back({b, w});
        g[b].push_back({a, w});
      }
    }
    tr = dynamic_star_min_cut_internal::build_contraction_tree(g);
    int rt = 2 * n - 2;
    build_heavy(rt);
    build_order(rt);

    std::vector<i64> bd(2 * n - 1);
    for (auto [a, b, w] : es1) {
      if (w == 0) {
        continue;
      }
      int l = a;
      int r = b;
      while (l != r) {
        if (dep[l] < dep[r]) {
          std::swap(l, r);
        }
        bd[l] += w;
        l = fa[l];
      }
    }
    for (int u = 0; u < 2 * n - 1; u++) {
      mn.set(ord[u], bd[u]);
    }
    for (int u = 0; u < n; u++) {
      assert(wt[u] >= 0);
      add_to_ancestors(u, wt[u]);
    }
  }

  /// Change one star-edge weight and return the new global minimum cut.
  i64 set_star_weight(int u, i64 w) {
    assert(0 <= u && u < nv && w >= 0);
    i64 res = add_to_ancestors(u, w - wt[u]);
    wt[u] = w;
    return res;
  }

  i64 global_min_cut() const { return mn.all_min(); }
};

} // namespace noya

#endif // NOYA_DYNAMIC_STAR_MIN_CUT_HPP
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

/// @complexity Time: O(n(n+m) log n + mn) preprocessing and
/// O(log^2 n) per update. Space: O(n+m).

namespace noya {

namespace dynamic_star_min_cut_internal {

using i64 = std::int64_t;
static constexpr i64 inf = i64(1) << 60;

class range_add_min_tree {
  int siz = 1;
  std::vector<i64> mn, lz;

  void apply(int nd, i64 ad1) {
    mn[nd] += ad1;
    lz[nd] += ad1;
  }

  void push(int nd) {
    apply(2 * nd + 1, lz[nd]);
    apply(2 * nd + 2, lz[nd]);
    lz[nd] = 0;
  }

  void add(int ql, int qr, int nd, int l, int r, i64 ad1) {
    if (r <= ql || qr <= l) {
      return;
    }
    if (ql <= l && r <= qr) {
      apply(nd, ad1);
      return;
    }
    push(nd);
    int mid = (l + r) / 2;
    add(ql, qr, 2 * nd + 1, l, mid, ad1);
    add(ql, qr, 2 * nd + 2, mid, r, ad1);
    mn[nd] = std::min(mn[2 * nd + 1], mn[2 * nd + 2]);
  }

public:
  explicit range_add_min_tree(int n) {
    while (siz < n) {
      siz *= 2;
    }
    mn.assign(2 * siz - 1, inf);
    lz.assign(2 * siz - 1, 0);
  }

  void add(int l, int r, i64 ad1) { add(l, r, 0, 0, siz, ad1); }

  i64 get(int i) const {
    int nd = i + siz - 1;
    i64 res = mn[nd];
    while (nd > 0) {
      nd = (nd - 1) / 2;
      res += lz[nd];
    }
    return res;
  }

  void set(int i, i64 val) { add(i, i + 1, val - get(i)); }

  i64 all_min() const { return mn[0]; }
};

struct weighted_neighbor {
  int u;
  i64 w;
};

/// Build the pendant-pair contraction tree.  Each merge creates the parent of
/// its two contracted clusters, so original vertices remain the leaves.
inline std::vector<std::vector<int>>
build_contraction_tree(std::vector<std::vector<weighted_neighbor>> g) {
  int n = int(g.size());
  std::vector<std::vector<int>> tr(2 * n - 1);
  std::vector<bool> act(2 * n - 1, true);
  for (int ph = 0; ph < n - 1; ph++) {
    g.push_back({});
    std::vector<int> ord(n - ph);
    std::vector<bool> vis(n + ph);
    std::vector<i64> es(n + ph);
    using heap_entry = std::pair<i64, int>;
    std::priority_queue<heap_entry> q;
    for (int u = 0; u < n + ph; u++) {
      if (!act[u]) {
        continue;
      }
      for (auto e : g[u]) {
        es[u] += e.w;
      }
      q.push({-es[u], u});
    }

    for (int pos = 0; pos < n - ph; pos++) {
      while (true) {
        auto [nc, u] = q.top();
        q.pop();
        if (vis[u]) {
          continue;
        }
        vis[u] = true;
        ord[pos] = u;
        for (auto e : g[u]) {
          if (!vis[e.u]) {
            es[e.u] -= e.w;
            q.push({-es[e.u], e.u});
          }
        }
        break;
      }
    }

    int a = ord[n - ph - 1];
    int b = ord[n - ph - 2];
    int mer = n + ph;
    g[a].clear();
    g[b].clear();
    act[a] = act[b] = false;
    for (int u = 0; u < mer; u++) {
      if (!act[u]) {
        continue;
      }
      for (weighted_neighbor &e : g[u]) {
        if (e.u == a || e.u == b) {
          e.u = mer;
          g[mer].push_back({u, e.w});
        }
      }
    }
    tr[mer].push_back(a);
    tr[mer].push_back(b);
  }
  return tr;
}

} // namespace dynamic_star_min_cut_internal

/// @brief Maintain the global min-cut after changing edges of an added star.
/// Repeated minimum-adjacency pendant-pair contractions build a binary cluster
/// tree with an optimal cut among its rooted clusters for every nonnegative
/// star weighting.  A cluster's cut is its original-graph boundary plus the
/// star weights of its leaves.  Thus a leaf update adds one value to all its
/// ancestors; heavy-light path updates and a global-min segment tree maintain
/// the best cluster cut.
class dynamic_star_min_cut {
public:
  using i64 = std::int64_t;
  struct edge {
    int a;
    int b;
    i64 w;
  };

private:
  int nv;
  std::vector<i64> wt;
  std::vector<std::vector<int>> tr;
  std::vector<int> fa, dep, son, hd, ord;
  dynamic_star_min_cut_internal::range_add_min_tree mn;

  int build_heavy(int u) {
    int sz = 1;
    int mc = 0;
    for (int v : tr[u]) {
      fa[v] = u;
      dep[v] = dep[u] + 1;
      int csz = build_heavy(v);
      sz += csz;
      if (csz > mc) {
        mc = csz;
        son[u] = v;
      }
    }
    return sz;
  }

  void build_order(int rt) {
    int ptr = 0;
    std::queue<int> q;
    q.push(rt);
    while (!q.empty()) {
      int ch = q.front();
      q.pop();
      for (int u = ch; u != -1; u = son[u]) {
        ord[u] = ptr++;
        hd[u] = ch;
        for (int v : tr[u]) {
          if (v != son[u]) {
            q.push(v);
          }
        }
      }
    }
  }

  i64 add_to_ancestors(int u, i64 ad1) {
    while (u >= 0) {
      mn.add(ord[hd[u]], ord[u] + 1, ad1);
      u = fa[hd[u]];
    }
    return mn.all_min();
  }

public:
  dynamic_star_min_cut(int n, const std::vector<edge> &es1, std::vector<i64> w0)
      : nv(n), wt(std::move(w0)), tr(), fa(2 * n - 1, -1), dep(2 * n - 1),
        son(2 * n - 1, -1), hd(2 * n - 1), ord(2 * n - 1, -1), mn(2 * n - 1) {
    assert(n >= 1 && int(wt.size()) == n);
    std::vector<std::vector<dynamic_star_min_cut_internal::weighted_neighbor>>
        g(n);
    for (auto [a, b, w] : es1) {
      assert(0 <= a && a < n && 0 <= b && b < n);
      assert(w >= 0);
      if (w != 0) {
        g[a].push_back({b, w});
        g[b].push_back({a, w});
      }
    }
    tr = dynamic_star_min_cut_internal::build_contraction_tree(g);
    int rt = 2 * n - 2;
    build_heavy(rt);
    build_order(rt);

    std::vector<i64> bd(2 * n - 1);
    for (auto [a, b, w] : es1) {
      if (w == 0) {
        continue;
      }
      int l = a;
      int r = b;
      while (l != r) {
        if (dep[l] < dep[r]) {
          std::swap(l, r);
        }
        bd[l] += w;
        l = fa[l];
      }
    }
    for (int u = 0; u < 2 * n - 1; u++) {
      mn.set(ord[u], bd[u]);
    }
    for (int u = 0; u < n; u++) {
      assert(wt[u] >= 0);
      add_to_ancestors(u, wt[u]);
    }
  }

  /// Change one star-edge weight and return the new global minimum cut.
  i64 set_star_weight(int u, i64 w) {
    assert(0 <= u && u < nv && w >= 0);
    i64 res = add_to_ancestors(u, w - wt[u]);
    wt[u] = w;
    return res;
  }

  i64 global_min_cut() const { return mn.all_min(); }
};

} // namespace noya