Skip to content

dynamic_star_min_cut.hpp

SECTIONGraph INCLUDEnoya/graph/dynamic_star_min_cut.hpp

固定非负无向图外加中心点,维护星形边权修改后的全局最小割;收缩建树、树上差分和免下推的区间加最小值。

Complexity: Time: \(O(n (n + m) \log (n + m + 1))\) preprocessing, \(O(\log^{2} (n + 1))\) per update, \(O(1)\) query. Space: \(O(n + m)\) build, \(O(n)\) retained. For simple graphs, preprocessing is \(O(n (n + m) \log (n + 1))\).

Source: Library Checker

AC 记录:global_minimum_cut_of_dynamic_star_augmented_graph。

跳到代码 · GitHub ↗

Implementation

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

// SPDX-License-Identifier: Apache-2.0
// Adapted from Library Checker, with changes for libra.
// Sources and license: third_party/README.md.

/// @complexity Time: O(n(n+m) log(n+m+1)) preprocessing,
/// O(log^2(n+1)) per update, O(1) query. Space: O(n+m) build, O(n) retained.
/// For simple graphs, preprocessing is O(n(n+m) log(n+1)).

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <queue>
#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;

  // mn includes this node's lazy value but excludes its ancestors' values.
  // Common additions commute with min, so partial updates need no push.
  void apply(int nd, i64 ad1) {
    mn[nd] += ad1;
    lz[nd] += ad1;
  }

  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;
    }
    int mid = (l + r) / 2;
    if (ql < mid)
      add(ql, qr, 2 * nd + 1, l, mid, ad1);
    if (mid < qr)
      add(ql, qr, 2 * nd + 2, mid, r, ad1);
    mn[nd] = lz[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 * std::size_t(siz) - 1, inf);
    lz.assign(mn.size(), 0);
  }

  void build(const std::vector<i64> &a) {
    assert(a.size() <= std::size_t(siz));
    std::fill(mn.begin(), mn.end(), inf);
    std::fill(lz.begin(), lz.end(), 0);
    std::copy(a.begin(), a.end(), mn.begin() + siz - 1);
    for (int i = siz - 2; i >= 0; --i)
      mn[i] = std::min(mn[2 * i + 1], mn[2 * i + 2]);
  }

  void add(int l, int r, i64 ad1) {
    if (l < r && ad1 != 0)
      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);
  g.resize(2 * n - 1);
  std::vector<int> active(n);
  for (int u = 0; u < n; ++u)
    active[u] = u;
  std::vector<char> vis(2 * n - 1);
  std::vector<i64> es(2 * n - 1);
  using heap_entry = std::pair<i64, int>;
  for (int mer = n; mer < 2 * n - 1; ++mer) {
    std::vector<heap_entry> init;
    init.reserve(active.size());
    for (int u : active) {
      vis[u] = false;
      es[u] = 0;
      for (auto e : g[u])
        es[u] += e.w;
      init.push_back({-es[u], u});
    }
    std::priority_queue<heap_entry> q(std::less<heap_entry>{}, std::move(init));
    int a = -1, b = -1;
    for (std::size_t pos = 0; pos < active.size(); ++pos) {
      int u;
      do {
        u = q.top().second;
        q.pop();
      } while (vis[u]);
      vis[u] = true;
      b = a;
      a = u;
      for (auto e : g[u])
        if (!vis[e.u]) {
          es[e.u] -= e.w;
          q.push({-es[e.u], e.u});
        }
    }
    // Clear capacity too: retired clusters must not retain O(n*m) buffers.
    std::vector<weighted_neighbor>().swap(g[a]);
    std::vector<weighted_neighbor>().swap(g[b]);
    std::size_t keep = 0;
    for (int u : active) {
      if (u == a || u == b)
        continue;
      active[keep++] = u;
      i64 sum = 0;
      std::size_t ptr = 0;
      for (auto e : g[u]) {
        if (e.u == a || e.u == b)
          sum += e.w;
        else
          g[u][ptr++] = e;
      }
      g[u].resize(ptr);
      if (sum) {
        g[u].push_back({mer, sum});
        g[mer].push_back({u, sum});
      }
    }
    active.resize(keep);
    active.push_back(mer);
    tr[mer] = {a, 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. Vertices are [0,n); require 1 <= n <= INT_MAX/4.
/// Edges and star weights are nonnegative. Parallel edges are allowed; loops
/// are ignored. Require the sum of all non-loop edge weights and current star
/// weights to stay below 2^60 (also at construction). This bounds all int64
/// intermediates and keeps real cuts below the segment tree's padding value.
/// Construction leaves inputs unchanged. Copies own independent state.
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<int> fa, hd, ord;
  dynamic_star_min_cut_internal::range_add_min_tree mn;

  static int tree_size(int n) {
    assert(1 <= n && n <= std::numeric_limits<int>::max() / 4);
    return 2 * n - 1;
  }

  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)), fa(tree_size(n), -1), hd(fa.size()),
        ord(fa.size()), mn(int(fa.size())) {
    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 (a != b && w != 0) {
        g[a].push_back({b, w});
        g[b].push_back({a, w});
      }
    }
    auto tr =
        dynamic_star_min_cut_internal::build_contraction_tree(std::move(g));
    std::vector<int> dep(2 * n - 1), son(2 * n - 1, -1);
    int rt = 2 * n - 2;
    // Every contraction parent has a larger id than both children.
    // Sizes accumulate forwards; depths propagate backwards, without DFS.
    std::vector<int> sz(2 * n - 1, 1);
    for (int u = n; u <= rt; ++u) {
      for (int v : tr[u]) {
        fa[v] = u;
        sz[u] += sz[v];
        if (son[u] == -1 || sz[v] > sz[son[u]])
          son[u] = v;
      }
    }
    for (int u = rt; u >= n; --u)
      for (int v : tr[u])
        dep[v] = dep[u] + 1;
    int ptr = 0;
    std::vector<int> queue{rt};
    for (std::size_t i = 0; i < queue.size(); ++i) {
      int ch = queue[i];
      for (int u = ch; u != -1; u = son[u]) {
        ord[u] = ptr++;
        hd[u] = ch;
        for (int v : tr[u])
          if (v != son[u])
            queue.push_back(v);
      }
    }

    // An edge contributes at both leaves and -2*w at their LCA. Forward
    // accumulation yields each cluster boundary plus its star-weight sum.
    std::vector<i64> bd(2 * n - 1);
    for (int u = 0; u < n; ++u) {
      assert(wt[u] >= 0);
      bd[u] = wt[u];
    }
    for (auto [a, b, w] : es1) {
      if (a == b || w == 0)
        continue;
      bd[a] += w;
      bd[b] += w;
      int l = a, r = b;
      while (hd[l] != hd[r]) {
        if (dep[hd[l]] < dep[hd[r]])
          std::swap(l, r);
        l = fa[hd[l]];
      }
      bd[dep[l] < dep[r] ? l : r] -= 2 * w;
    }
    for (int u = 0; u < rt; ++u)
      bd[fa[u]] += bd[u];
    std::vector<i64> init(2 * n - 1);
    for (int u = 0; u <= rt; ++u)
      init[ord[u]] = bd[u];
    mn.build(init);
  }

  /// 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);
    if (w == wt[u])
      return mn.all_min();
    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

 // NOYA_DYNAMIC_STAR_MIN_CUT_HPP
#ifndef NOYA_DYNAMIC_STAR_MIN_CUT_HPP
#define NOYA_DYNAMIC_STAR_MIN_CUT_HPP 1

// SPDX-License-Identifier: Apache-2.0
// Adapted from Library Checker, with changes for libra.
// Sources and license: third_party/README.md.

/// @complexity Time: O(n(n+m) log(n+m+1)) preprocessing,
/// O(log^2(n+1)) per update, O(1) query. Space: O(n+m) build, O(n) retained.
/// For simple graphs, preprocessing is O(n(n+m) log(n+1)).

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <queue>
#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;

  // mn includes this node's lazy value but excludes its ancestors' values.
  // Common additions commute with min, so partial updates need no push.
  void apply(int nd, i64 ad1) {
    mn[nd] += ad1;
    lz[nd] += ad1;
  }

  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;
    }
    int mid = (l + r) / 2;
    if (ql < mid)
      add(ql, qr, 2 * nd + 1, l, mid, ad1);
    if (mid < qr)
      add(ql, qr, 2 * nd + 2, mid, r, ad1);
    mn[nd] = lz[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 * std::size_t(siz) - 1, inf);
    lz.assign(mn.size(), 0);
  }

  void build(const std::vector<i64> &a) {
    assert(a.size() <= std::size_t(siz));
    std::fill(mn.begin(), mn.end(), inf);
    std::fill(lz.begin(), lz.end(), 0);
    std::copy(a.begin(), a.end(), mn.begin() + siz - 1);
    for (int i = siz - 2; i >= 0; --i)
      mn[i] = std::min(mn[2 * i + 1], mn[2 * i + 2]);
  }

  void add(int l, int r, i64 ad1) {
    if (l < r && ad1 != 0)
      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);
  g.resize(2 * n - 1);
  std::vector<int> active(n);
  for (int u = 0; u < n; ++u)
    active[u] = u;
  std::vector<char> vis(2 * n - 1);
  std::vector<i64> es(2 * n - 1);
  using heap_entry = std::pair<i64, int>;
  for (int mer = n; mer < 2 * n - 1; ++mer) {
    std::vector<heap_entry> init;
    init.reserve(active.size());
    for (int u : active) {
      vis[u] = false;
      es[u] = 0;
      for (auto e : g[u])
        es[u] += e.w;
      init.push_back({-es[u], u});
    }
    std::priority_queue<heap_entry> q(std::less<heap_entry>{}, std::move(init));
    int a = -1, b = -1;
    for (std::size_t pos = 0; pos < active.size(); ++pos) {
      int u;
      do {
        u = q.top().second;
        q.pop();
      } while (vis[u]);
      vis[u] = true;
      b = a;
      a = u;
      for (auto e : g[u])
        if (!vis[e.u]) {
          es[e.u] -= e.w;
          q.push({-es[e.u], e.u});
        }
    }
    // Clear capacity too: retired clusters must not retain O(n*m) buffers.
    std::vector<weighted_neighbor>().swap(g[a]);
    std::vector<weighted_neighbor>().swap(g[b]);
    std::size_t keep = 0;
    for (int u : active) {
      if (u == a || u == b)
        continue;
      active[keep++] = u;
      i64 sum = 0;
      std::size_t ptr = 0;
      for (auto e : g[u]) {
        if (e.u == a || e.u == b)
          sum += e.w;
        else
          g[u][ptr++] = e;
      }
      g[u].resize(ptr);
      if (sum) {
        g[u].push_back({mer, sum});
        g[mer].push_back({u, sum});
      }
    }
    active.resize(keep);
    active.push_back(mer);
    tr[mer] = {a, 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. Vertices are [0,n); require 1 <= n <= INT_MAX/4.
/// Edges and star weights are nonnegative. Parallel edges are allowed; loops
/// are ignored. Require the sum of all non-loop edge weights and current star
/// weights to stay below 2^60 (also at construction). This bounds all int64
/// intermediates and keeps real cuts below the segment tree's padding value.
/// Construction leaves inputs unchanged. Copies own independent state.
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<int> fa, hd, ord;
  dynamic_star_min_cut_internal::range_add_min_tree mn;

  static int tree_size(int n) {
    assert(1 <= n && n <= std::numeric_limits<int>::max() / 4);
    return 2 * n - 1;
  }

  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)), fa(tree_size(n), -1), hd(fa.size()),
        ord(fa.size()), mn(int(fa.size())) {
    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 (a != b && w != 0) {
        g[a].push_back({b, w});
        g[b].push_back({a, w});
      }
    }
    auto tr =
        dynamic_star_min_cut_internal::build_contraction_tree(std::move(g));
    std::vector<int> dep(2 * n - 1), son(2 * n - 1, -1);
    int rt = 2 * n - 2;
    // Every contraction parent has a larger id than both children.
    // Sizes accumulate forwards; depths propagate backwards, without DFS.
    std::vector<int> sz(2 * n - 1, 1);
    for (int u = n; u <= rt; ++u) {
      for (int v : tr[u]) {
        fa[v] = u;
        sz[u] += sz[v];
        if (son[u] == -1 || sz[v] > sz[son[u]])
          son[u] = v;
      }
    }
    for (int u = rt; u >= n; --u)
      for (int v : tr[u])
        dep[v] = dep[u] + 1;
    int ptr = 0;
    std::vector<int> queue{rt};
    for (std::size_t i = 0; i < queue.size(); ++i) {
      int ch = queue[i];
      for (int u = ch; u != -1; u = son[u]) {
        ord[u] = ptr++;
        hd[u] = ch;
        for (int v : tr[u])
          if (v != son[u])
            queue.push_back(v);
      }
    }

    // An edge contributes at both leaves and -2*w at their LCA. Forward
    // accumulation yields each cluster boundary plus its star-weight sum.
    std::vector<i64> bd(2 * n - 1);
    for (int u = 0; u < n; ++u) {
      assert(wt[u] >= 0);
      bd[u] = wt[u];
    }
    for (auto [a, b, w] : es1) {
      if (a == b || w == 0)
        continue;
      bd[a] += w;
      bd[b] += w;
      int l = a, r = b;
      while (hd[l] != hd[r]) {
        if (dep[hd[l]] < dep[hd[r]])
          std::swap(l, r);
        l = fa[hd[l]];
      }
      bd[dep[l] < dep[r] ? l : r] -= 2 * w;
    }
    for (int u = 0; u < rt; ++u)
      bd[fa[u]] += bd[u];
    std::vector<i64> init(2 * n - 1);
    for (int u = 0; u <= rt; ++u)
      init[ord[u]] = bd[u];
    mn.build(init);
  }

  /// 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);
    if (w == wt[u])
      return mn.all_min();
    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
// SPDX-License-Identifier: Apache-2.0
// Adapted from Library Checker, with changes for libra.
// Sources and license: third_party/README.md.

/// @complexity Time: O(n(n+m) log(n+m+1)) preprocessing,
/// O(log^2(n+1)) per update, O(1) query. Space: O(n+m) build, O(n) retained.
/// For simple graphs, preprocessing is O(n(n+m) log(n+1)).

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <queue>
#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;

  // mn includes this node's lazy value but excludes its ancestors' values.
  // Common additions commute with min, so partial updates need no push.
  void apply(int nd, i64 ad1) {
    mn[nd] += ad1;
    lz[nd] += ad1;
  }

  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;
    }
    int mid = (l + r) / 2;
    if (ql < mid)
      add(ql, qr, 2 * nd + 1, l, mid, ad1);
    if (mid < qr)
      add(ql, qr, 2 * nd + 2, mid, r, ad1);
    mn[nd] = lz[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 * std::size_t(siz) - 1, inf);
    lz.assign(mn.size(), 0);
  }

  void build(const std::vector<i64> &a) {
    assert(a.size() <= std::size_t(siz));
    std::fill(mn.begin(), mn.end(), inf);
    std::fill(lz.begin(), lz.end(), 0);
    std::copy(a.begin(), a.end(), mn.begin() + siz - 1);
    for (int i = siz - 2; i >= 0; --i)
      mn[i] = std::min(mn[2 * i + 1], mn[2 * i + 2]);
  }

  void add(int l, int r, i64 ad1) {
    if (l < r && ad1 != 0)
      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);
  g.resize(2 * n - 1);
  std::vector<int> active(n);
  for (int u = 0; u < n; ++u)
    active[u] = u;
  std::vector<char> vis(2 * n - 1);
  std::vector<i64> es(2 * n - 1);
  using heap_entry = std::pair<i64, int>;
  for (int mer = n; mer < 2 * n - 1; ++mer) {
    std::vector<heap_entry> init;
    init.reserve(active.size());
    for (int u : active) {
      vis[u] = false;
      es[u] = 0;
      for (auto e : g[u])
        es[u] += e.w;
      init.push_back({-es[u], u});
    }
    std::priority_queue<heap_entry> q(std::less<heap_entry>{}, std::move(init));
    int a = -1, b = -1;
    for (std::size_t pos = 0; pos < active.size(); ++pos) {
      int u;
      do {
        u = q.top().second;
        q.pop();
      } while (vis[u]);
      vis[u] = true;
      b = a;
      a = u;
      for (auto e : g[u])
        if (!vis[e.u]) {
          es[e.u] -= e.w;
          q.push({-es[e.u], e.u});
        }
    }
    // Clear capacity too: retired clusters must not retain O(n*m) buffers.
    std::vector<weighted_neighbor>().swap(g[a]);
    std::vector<weighted_neighbor>().swap(g[b]);
    std::size_t keep = 0;
    for (int u : active) {
      if (u == a || u == b)
        continue;
      active[keep++] = u;
      i64 sum = 0;
      std::size_t ptr = 0;
      for (auto e : g[u]) {
        if (e.u == a || e.u == b)
          sum += e.w;
        else
          g[u][ptr++] = e;
      }
      g[u].resize(ptr);
      if (sum) {
        g[u].push_back({mer, sum});
        g[mer].push_back({u, sum});
      }
    }
    active.resize(keep);
    active.push_back(mer);
    tr[mer] = {a, 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. Vertices are [0,n); require 1 <= n <= INT_MAX/4.
/// Edges and star weights are nonnegative. Parallel edges are allowed; loops
/// are ignored. Require the sum of all non-loop edge weights and current star
/// weights to stay below 2^60 (also at construction). This bounds all int64
/// intermediates and keeps real cuts below the segment tree's padding value.
/// Construction leaves inputs unchanged. Copies own independent state.
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<int> fa, hd, ord;
  dynamic_star_min_cut_internal::range_add_min_tree mn;

  static int tree_size(int n) {
    assert(1 <= n && n <= std::numeric_limits<int>::max() / 4);
    return 2 * n - 1;
  }

  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)), fa(tree_size(n), -1), hd(fa.size()),
        ord(fa.size()), mn(int(fa.size())) {
    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 (a != b && w != 0) {
        g[a].push_back({b, w});
        g[b].push_back({a, w});
      }
    }
    auto tr =
        dynamic_star_min_cut_internal::build_contraction_tree(std::move(g));
    std::vector<int> dep(2 * n - 1), son(2 * n - 1, -1);
    int rt = 2 * n - 2;
    // Every contraction parent has a larger id than both children.
    // Sizes accumulate forwards; depths propagate backwards, without DFS.
    std::vector<int> sz(2 * n - 1, 1);
    for (int u = n; u <= rt; ++u) {
      for (int v : tr[u]) {
        fa[v] = u;
        sz[u] += sz[v];
        if (son[u] == -1 || sz[v] > sz[son[u]])
          son[u] = v;
      }
    }
    for (int u = rt; u >= n; --u)
      for (int v : tr[u])
        dep[v] = dep[u] + 1;
    int ptr = 0;
    std::vector<int> queue{rt};
    for (std::size_t i = 0; i < queue.size(); ++i) {
      int ch = queue[i];
      for (int u = ch; u != -1; u = son[u]) {
        ord[u] = ptr++;
        hd[u] = ch;
        for (int v : tr[u])
          if (v != son[u])
            queue.push_back(v);
      }
    }

    // An edge contributes at both leaves and -2*w at their LCA. Forward
    // accumulation yields each cluster boundary plus its star-weight sum.
    std::vector<i64> bd(2 * n - 1);
    for (int u = 0; u < n; ++u) {
      assert(wt[u] >= 0);
      bd[u] = wt[u];
    }
    for (auto [a, b, w] : es1) {
      if (a == b || w == 0)
        continue;
      bd[a] += w;
      bd[b] += w;
      int l = a, r = b;
      while (hd[l] != hd[r]) {
        if (dep[hd[l]] < dep[hd[r]])
          std::swap(l, r);
        l = fa[hd[l]];
      }
      bd[dep[l] < dep[r] ? l : r] -= 2 * w;
    }
    for (int u = 0; u < rt; ++u)
      bd[fa[u]] += bd[u];
    std::vector<i64> init(2 * n - 1);
    for (int u = 0; u <= rt; ++u)
      init[ord[u]] = bd[u];
    mn.build(init);
  }

  /// 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);
    if (w == wt[u])
      return mn.all_min();
    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

 // NOYA_DYNAMIC_STAR_MIN_CUT_HPP