Skip to content

rooted_tree_minimum_inversion_order.hpp

SECTIONGraph INCLUDEnoya/rooted_tree_minimum_inversion_order.hpp

为有根树安排满足祖先约束的线性序,使给定权值产生的逆序对最少。

Complexity: Time: O(n log n). Space: O(n).

AC 记录:rooted_tree_topological_order_with_minimum_inversions

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(n log n). Space: O(n).

#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;
};

namespace internal {

class labeled_dsu {
  std::vector<int> fa_;
  std::vector<int> id_;

  int leader(int u) {
    int rt = u;
    while (fa_[rt] >= 0) {
      rt = fa_[rt];
    }
    while (u != rt) {
      int nxt = fa_[u];
      fa_[u] = rt;
      u = nxt;
    }
    return rt;
  }

public:
  explicit labeled_dsu(int siz) : fa_(siz, -1), id_(siz) {
    std::iota(id_.begin(), id_.end(), 0);
  }

  int label(int u) { return id_[leader(u)]; }

  void merge(int a, int b, int nl) {
    a = leader(a);
    b = leader(b);
    assert(a != b);
    if (fa_[a] > fa_[b]) {
      std::swap(a, b);
    }
    fa_[a] += fa_[b];
    fa_[b] = a;
    id_[a] = nl;
  }
};

} // namespace internal

/// @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 labeled DSU finds that current parent 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 and their products.
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 {
    static bool ratio_less(const block &l, const block &r) {
      bool lz = l.c == Weight{} && l.d == Weight{};
      bool rz = r.c == Weight{} && r.d == Weight{};
      if (lz != rz) {
        return lz;
      }
      if (lz) {
        return false;
      }
      return l.c * r.d < l.d * r.c;
    }

    bool operator()(const block &l, const block &r) const {
      if (ratio_less(l, r)) {
        return true;
      }
      if (ratio_less(r, l)) {
        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::priority_queue<block, std::vector<block>, lower_priority> q;
  for (int u = 0; u < siz; u++) {
    if (u != rt) {
      q.push({bd[u], bc[u], u, 0});
    }
  }

  internal::labeled_dsu dsu(siz);
  std::vector<int> nxt(siz);
  std::iota(nxt.begin(), nxt.end(), 0);
  while (!q.empty()) {
    block cur = q.top();
    q.pop();
    int u = cur.rt;
    if (cur.ver != ver[u]) {
      continue;
    }
    int bp = dsu.label(fa[u]);
    bc[bp] += bc[u];
    bd[bp] += bd[u];
    dsu.merge(u, bp, bp);
    if (bp != rt) {
      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.reserve(siz);
  int u = rt;
  for (int i = 0; i < siz; i++) {
    u = nxt[u];
    res.ord.push_back(u);
  }
  assert(u == rt);
  std::reverse(res.ord.begin(), res.ord.end());

  Weight pd{};
  for (int cur : res.ord) {
    res.cst += pd * c[cur];
    pd += d[cur];
  }
  return res;
}

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

/// @complexity Time: O(n log n). Space: O(n).

#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;
};

namespace internal {

class labeled_dsu {
  std::vector<int> fa_;
  std::vector<int> id_;

  int leader(int u) {
    int rt = u;
    while (fa_[rt] >= 0) {
      rt = fa_[rt];
    }
    while (u != rt) {
      int nxt = fa_[u];
      fa_[u] = rt;
      u = nxt;
    }
    return rt;
  }

public:
  explicit labeled_dsu(int siz) : fa_(siz, -1), id_(siz) {
    std::iota(id_.begin(), id_.end(), 0);
  }

  int label(int u) { return id_[leader(u)]; }

  void merge(int a, int b, int nl) {
    a = leader(a);
    b = leader(b);
    assert(a != b);
    if (fa_[a] > fa_[b]) {
      std::swap(a, b);
    }
    fa_[a] += fa_[b];
    fa_[b] = a;
    id_[a] = nl;
  }
};

} // namespace internal

/// @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 labeled DSU finds that current parent 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 and their products.
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 {
    static bool ratio_less(const block &l, const block &r) {
      bool lz = l.c == Weight{} && l.d == Weight{};
      bool rz = r.c == Weight{} && r.d == Weight{};
      if (lz != rz) {
        return lz;
      }
      if (lz) {
        return false;
      }
      return l.c * r.d < l.d * r.c;
    }

    bool operator()(const block &l, const block &r) const {
      if (ratio_less(l, r)) {
        return true;
      }
      if (ratio_less(r, l)) {
        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::priority_queue<block, std::vector<block>, lower_priority> q;
  for (int u = 0; u < siz; u++) {
    if (u != rt) {
      q.push({bd[u], bc[u], u, 0});
    }
  }

  internal::labeled_dsu dsu(siz);
  std::vector<int> nxt(siz);
  std::iota(nxt.begin(), nxt.end(), 0);
  while (!q.empty()) {
    block cur = q.top();
    q.pop();
    int u = cur.rt;
    if (cur.ver != ver[u]) {
      continue;
    }
    int bp = dsu.label(fa[u]);
    bc[bp] += bc[u];
    bd[bp] += bd[u];
    dsu.merge(u, bp, bp);
    if (bp != rt) {
      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.reserve(siz);
  int u = rt;
  for (int i = 0; i < siz; i++) {
    u = nxt[u];
    res.ord.push_back(u);
  }
  assert(u == rt);
  std::reverse(res.ord.begin(), res.ord.end());

  Weight pd{};
  for (int cur : res.ord) {
    res.cst += pd * c[cur];
    pd += d[cur];
  }
  return res;
}

} // namespace noya

#endif // NOYA_ROOTED_TREE_MINIMUM_INVERSION_ORDER_HPP
#include <algorithm>
#include <cassert>
#include <numeric>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

/// @complexity Time: O(n log n). Space: O(n).

namespace noya {

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

namespace internal {

class labeled_dsu {
  std::vector<int> fa_;
  std::vector<int> id_;

  int leader(int u) {
    int rt = u;
    while (fa_[rt] >= 0) {
      rt = fa_[rt];
    }
    while (u != rt) {
      int nxt = fa_[u];
      fa_[u] = rt;
      u = nxt;
    }
    return rt;
  }

public:
  explicit labeled_dsu(int siz) : fa_(siz, -1), id_(siz) {
    std::iota(id_.begin(), id_.end(), 0);
  }

  int label(int u) { return id_[leader(u)]; }

  void merge(int a, int b, int nl) {
    a = leader(a);
    b = leader(b);
    assert(a != b);
    if (fa_[a] > fa_[b]) {
      std::swap(a, b);
    }
    fa_[a] += fa_[b];
    fa_[b] = a;
    id_[a] = nl;
  }
};

} // namespace internal

/// @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 labeled DSU finds that current parent 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 and their products.
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 {
    static bool ratio_less(const block &l, const block &r) {
      bool lz = l.c == Weight{} && l.d == Weight{};
      bool rz = r.c == Weight{} && r.d == Weight{};
      if (lz != rz) {
        return lz;
      }
      if (lz) {
        return false;
      }
      return l.c * r.d < l.d * r.c;
    }

    bool operator()(const block &l, const block &r) const {
      if (ratio_less(l, r)) {
        return true;
      }
      if (ratio_less(r, l)) {
        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::priority_queue<block, std::vector<block>, lower_priority> q;
  for (int u = 0; u < siz; u++) {
    if (u != rt) {
      q.push({bd[u], bc[u], u, 0});
    }
  }

  internal::labeled_dsu dsu(siz);
  std::vector<int> nxt(siz);
  std::iota(nxt.begin(), nxt.end(), 0);
  while (!q.empty()) {
    block cur = q.top();
    q.pop();
    int u = cur.rt;
    if (cur.ver != ver[u]) {
      continue;
    }
    int bp = dsu.label(fa[u]);
    bc[bp] += bc[u];
    bd[bp] += bd[u];
    dsu.merge(u, bp, bp);
    if (bp != rt) {
      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.reserve(siz);
  int u = rt;
  for (int i = 0; i < siz; i++) {
    u = nxt[u];
    res.ord.push_back(u);
  }
  assert(u == rt);
  std::reverse(res.ord.begin(), res.ord.end());

  Weight pd{};
  for (int cur : res.ord) {
    res.cst += pd * c[cur];
    pd += d[cur];
  }
  return res;
}

} // namespace noya