Skip to content

rooted_tree_minimum_inversion_order.hpp

SECTIONGraph INCLUDEnoya/graph/rooted_tree_minimum_inversion_order.hpp

对非负权 c、d,求父亲在孩子之前且 sum(i<j) d[ord[i]]*c[ord[j]] 最小的排列;按块的 c/d 收缩到父块,用路径压缩和环形链表恢复顺序,\(O(n\log n)\)。

Complexity: Time: \(O(n \log (n + 1))\) with constant-time Weight arithmetic. Space: \(O(n)\), including worst-case \(O(n)\) path-compression recursion.

AC 记录:rooted_tree_topological_order_with_minimum_inversions。

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(n log(n+1)) with constant-time Weight arithmetic.
/// Space: O(n), including worst-case O(n) path-compression recursion.

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

namespace noya {

template <class Weight> struct rooted_tree_minimum_inversion_order_result {
  Weight cst{};
  std::vector<int> ord;
};

/// @brief Find a parent-before-child order minimizing the weighted inversion
/// cost. For two independent blocks A and B, placing A first contributes
/// d(A)c(B), while placing B first contributes d(B)c(A); hence the better
/// block order is decreasing c/d. Sidney's decomposition for an out-tree is
/// obtained by repeatedly contracting the maximum-ratio non-root block into
/// its parent block. A path-compressed parent link finds that block, while a
/// cyclic linked list records the corresponding concatenations. The returned
/// cost is sum over i < j of d[ord[i]] * c[ord[j]]. All weights must be
/// nonnegative, and Weight must hold aggregate sums, products and the cost.
/// fa must describe one nonempty tree, with exactly one -1 root. Inputs are
/// unchanged. Equal ratios prefer the larger block root, then version;
/// (c,d) == (0,0) has lower priority than every nonzero block.
template <class Weight>
rooted_tree_minimum_inversion_order_result<Weight>
rooted_tree_minimum_inversion_order(const std::vector<int> &fa,
                                    const std::vector<Weight> &c,
                                    const std::vector<Weight> &d) {
  int siz = int(fa.size());
  assert(siz > 0);
  assert(int(c.size()) == siz && int(d.size()) == siz);
  int rt = -1;
  for (int u = 0; u < siz; u++) {
    assert(c[u] >= Weight{} && d[u] >= Weight{});
    if (fa[u] == -1) {
      assert(rt == -1);
      rt = u;
    } else {
      assert(0 <= fa[u] && fa[u] < siz);
      assert(fa[u] != u);
    }
  }
  assert(rt != -1);

  struct block {
    Weight d;
    Weight c;
    int rt;
    int ver;
  };
  struct lower_priority {
    bool operator()(const block &l, const block &r) const {
      bool lz = l.c == Weight{} && l.d == Weight{};
      bool rz = r.c == Weight{} && r.d == Weight{};
      if (lz != rz) return lz;
      if (!lz) {
        Weight lhs = l.c * r.d, rhs = l.d * r.c;
        if (lhs < rhs) return true;
        if (rhs < lhs) return false;
      }
      return std::tie(l.rt, l.ver) < std::tie(r.rt, r.ver);
    }
  };

  std::vector<Weight> bc = c;
  std::vector<Weight> bd = d;
  std::vector<int> ver(siz);
  std::vector<block> initial;
  initial.reserve(siz - 1);
  for (int u = 0; u < siz; u++) {
    if (u != rt) {
      initial.push_back({bd[u], bc[u], u, 0});
    }
  }

  std::priority_queue<block, std::vector<block>, lower_priority> q(
      lower_priority{}, std::move(initial));
  std::vector<int> link(siz);
  std::iota(link.begin(), link.end(), 0);
  auto leader = [&](auto &self, int u) -> int {
    return link[u] == u ? u : link[u] = self(self, link[u]);
  };
  std::vector<int> nxt(siz);
  std::iota(nxt.begin(), nxt.end(), 0);
  for (int merged = 0; merged < siz - 1;) {
    assert(!q.empty());
    block cur = q.top();
    q.pop();
    int u = cur.rt;
    if (cur.ver != ver[u]) {
      continue;
    }
    int bp = leader(leader, fa[u]);
    link[u] = bp; // A live block's representative is its highest vertex.
    ++merged;
    if (bp != rt) {
      bc[bp] += bc[u];
      bd[bp] += bd[u];
      int ve1 = ++ver[bp];
      q.push({bd[bp], bc[bp], bp, ve1});
    }
    std::swap(nxt[u], nxt[bp]);
  }

  rooted_tree_minimum_inversion_order_result<Weight> res;
  res.ord.resize(siz);
  int u = rt;
  for (int i = siz - 1; i >= 0; --i) {
    u = nxt[u];
    res.ord[i] = u;
  }
  assert(u == rt);

  Weight pd{};
  for (int i = 0; i < siz; ++i) {
    int cur = res.ord[i];
    res.cst += pd * c[cur];
    if (i + 1 < siz) pd += d[cur];
  }
  return res;
}

} // namespace noya

 // NOYA_ROOTED_TREE_MINIMUM_INVERSION_ORDER_HPP
#ifndef NOYA_ROOTED_TREE_MINIMUM_INVERSION_ORDER_HPP
#define NOYA_ROOTED_TREE_MINIMUM_INVERSION_ORDER_HPP 1

/// @complexity Time: O(n log(n+1)) with constant-time Weight arithmetic.
/// Space: O(n), including worst-case O(n) path-compression recursion.

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

namespace noya {

template <class Weight> struct rooted_tree_minimum_inversion_order_result {
  Weight cst{};
  std::vector<int> ord;
};

/// @brief Find a parent-before-child order minimizing the weighted inversion
/// cost. For two independent blocks A and B, placing A first contributes
/// d(A)c(B), while placing B first contributes d(B)c(A); hence the better
/// block order is decreasing c/d. Sidney's decomposition for an out-tree is
/// obtained by repeatedly contracting the maximum-ratio non-root block into
/// its parent block. A path-compressed parent link finds that block, while a
/// cyclic linked list records the corresponding concatenations. The returned
/// cost is sum over i < j of d[ord[i]] * c[ord[j]]. All weights must be
/// nonnegative, and Weight must hold aggregate sums, products and the cost.
/// fa must describe one nonempty tree, with exactly one -1 root. Inputs are
/// unchanged. Equal ratios prefer the larger block root, then version;
/// (c,d) == (0,0) has lower priority than every nonzero block.
template <class Weight>
rooted_tree_minimum_inversion_order_result<Weight>
rooted_tree_minimum_inversion_order(const std::vector<int> &fa,
                                    const std::vector<Weight> &c,
                                    const std::vector<Weight> &d) {
  int siz = int(fa.size());
  assert(siz > 0);
  assert(int(c.size()) == siz && int(d.size()) == siz);
  int rt = -1;
  for (int u = 0; u < siz; u++) {
    assert(c[u] >= Weight{} && d[u] >= Weight{});
    if (fa[u] == -1) {
      assert(rt == -1);
      rt = u;
    } else {
      assert(0 <= fa[u] && fa[u] < siz);
      assert(fa[u] != u);
    }
  }
  assert(rt != -1);

  struct block {
    Weight d;
    Weight c;
    int rt;
    int ver;
  };
  struct lower_priority {
    bool operator()(const block &l, const block &r) const {
      bool lz = l.c == Weight{} && l.d == Weight{};
      bool rz = r.c == Weight{} && r.d == Weight{};
      if (lz != rz) return lz;
      if (!lz) {
        Weight lhs = l.c * r.d, rhs = l.d * r.c;
        if (lhs < rhs) return true;
        if (rhs < lhs) return false;
      }
      return std::tie(l.rt, l.ver) < std::tie(r.rt, r.ver);
    }
  };

  std::vector<Weight> bc = c;
  std::vector<Weight> bd = d;
  std::vector<int> ver(siz);
  std::vector<block> initial;
  initial.reserve(siz - 1);
  for (int u = 0; u < siz; u++) {
    if (u != rt) {
      initial.push_back({bd[u], bc[u], u, 0});
    }
  }

  std::priority_queue<block, std::vector<block>, lower_priority> q(
      lower_priority{}, std::move(initial));
  std::vector<int> link(siz);
  std::iota(link.begin(), link.end(), 0);
  auto leader = [&](auto &self, int u) -> int {
    return link[u] == u ? u : link[u] = self(self, link[u]);
  };
  std::vector<int> nxt(siz);
  std::iota(nxt.begin(), nxt.end(), 0);
  for (int merged = 0; merged < siz - 1;) {
    assert(!q.empty());
    block cur = q.top();
    q.pop();
    int u = cur.rt;
    if (cur.ver != ver[u]) {
      continue;
    }
    int bp = leader(leader, fa[u]);
    link[u] = bp; // A live block's representative is its highest vertex.
    ++merged;
    if (bp != rt) {
      bc[bp] += bc[u];
      bd[bp] += bd[u];
      int ve1 = ++ver[bp];
      q.push({bd[bp], bc[bp], bp, ve1});
    }
    std::swap(nxt[u], nxt[bp]);
  }

  rooted_tree_minimum_inversion_order_result<Weight> res;
  res.ord.resize(siz);
  int u = rt;
  for (int i = siz - 1; i >= 0; --i) {
    u = nxt[u];
    res.ord[i] = u;
  }
  assert(u == rt);

  Weight pd{};
  for (int i = 0; i < siz; ++i) {
    int cur = res.ord[i];
    res.cst += pd * c[cur];
    if (i + 1 < siz) pd += d[cur];
  }
  return res;
}

} // namespace noya

#endif // NOYA_ROOTED_TREE_MINIMUM_INVERSION_ORDER_HPP
/// @complexity Time: O(n log(n+1)) with constant-time Weight arithmetic.
/// Space: O(n), including worst-case O(n) path-compression recursion.

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

namespace noya {

template <class Weight> struct rooted_tree_minimum_inversion_order_result {
  Weight cst{};
  std::vector<int> ord;
};

/// @brief Find a parent-before-child order minimizing the weighted inversion
/// cost. For two independent blocks A and B, placing A first contributes
/// d(A)c(B), while placing B first contributes d(B)c(A); hence the better
/// block order is decreasing c/d. Sidney's decomposition for an out-tree is
/// obtained by repeatedly contracting the maximum-ratio non-root block into
/// its parent block. A path-compressed parent link finds that block, while a
/// cyclic linked list records the corresponding concatenations. The returned
/// cost is sum over i < j of d[ord[i]] * c[ord[j]]. All weights must be
/// nonnegative, and Weight must hold aggregate sums, products and the cost.
/// fa must describe one nonempty tree, with exactly one -1 root. Inputs are
/// unchanged. Equal ratios prefer the larger block root, then version;
/// (c,d) == (0,0) has lower priority than every nonzero block.
template <class Weight>
rooted_tree_minimum_inversion_order_result<Weight>
rooted_tree_minimum_inversion_order(const std::vector<int> &fa,
                                    const std::vector<Weight> &c,
                                    const std::vector<Weight> &d) {
  int siz = int(fa.size());
  assert(siz > 0);
  assert(int(c.size()) == siz && int(d.size()) == siz);
  int rt = -1;
  for (int u = 0; u < siz; u++) {
    assert(c[u] >= Weight{} && d[u] >= Weight{});
    if (fa[u] == -1) {
      assert(rt == -1);
      rt = u;
    } else {
      assert(0 <= fa[u] && fa[u] < siz);
      assert(fa[u] != u);
    }
  }
  assert(rt != -1);

  struct block {
    Weight d;
    Weight c;
    int rt;
    int ver;
  };
  struct lower_priority {
    bool operator()(const block &l, const block &r) const {
      bool lz = l.c == Weight{} && l.d == Weight{};
      bool rz = r.c == Weight{} && r.d == Weight{};
      if (lz != rz) return lz;
      if (!lz) {
        Weight lhs = l.c * r.d, rhs = l.d * r.c;
        if (lhs < rhs) return true;
        if (rhs < lhs) return false;
      }
      return std::tie(l.rt, l.ver) < std::tie(r.rt, r.ver);
    }
  };

  std::vector<Weight> bc = c;
  std::vector<Weight> bd = d;
  std::vector<int> ver(siz);
  std::vector<block> initial;
  initial.reserve(siz - 1);
  for (int u = 0; u < siz; u++) {
    if (u != rt) {
      initial.push_back({bd[u], bc[u], u, 0});
    }
  }

  std::priority_queue<block, std::vector<block>, lower_priority> q(
      lower_priority{}, std::move(initial));
  std::vector<int> link(siz);
  std::iota(link.begin(), link.end(), 0);
  auto leader = [&](auto &self, int u) -> int {
    return link[u] == u ? u : link[u] = self(self, link[u]);
  };
  std::vector<int> nxt(siz);
  std::iota(nxt.begin(), nxt.end(), 0);
  for (int merged = 0; merged < siz - 1;) {
    assert(!q.empty());
    block cur = q.top();
    q.pop();
    int u = cur.rt;
    if (cur.ver != ver[u]) {
      continue;
    }
    int bp = leader(leader, fa[u]);
    link[u] = bp; // A live block's representative is its highest vertex.
    ++merged;
    if (bp != rt) {
      bc[bp] += bc[u];
      bd[bp] += bd[u];
      int ve1 = ++ver[bp];
      q.push({bd[bp], bc[bp], bp, ve1});
    }
    std::swap(nxt[u], nxt[bp]);
  }

  rooted_tree_minimum_inversion_order_result<Weight> res;
  res.ord.resize(siz);
  int u = rt;
  for (int i = siz - 1; i >= 0; --i) {
    u = nxt[u];
    res.ord[i] = u;
  }
  assert(u == rt);

  Weight pd{};
  for (int i = 0; i < siz; ++i) {
    int cur = res.ord[i];
    res.cst += pd * c[cur];
    if (i + 1 < siz) pd += d[cur];
  }
  return res;
}

} // namespace noya

 // NOYA_ROOTED_TREE_MINIMUM_INVERSION_ORDER_HPP