Skip to content

li_chao_tree.hpp

SECTIONOptimization INCLUDEnoya/li_chao_tree.hpp

动态加入直线或线段,并在坐标点查询最小/最大函数值;适合斜率和查询顺序都不单调。

Complexity: Time: O(log n) per line/segment insertion or point query on n coordinates. Space: O(n).

AC 记录:line_add_get_min, segment_add_get_min

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(log n) per line/segment insertion or point query on n coordinates.
/// Space: O(n).

#include <algorithm>
#include <cassert>
#include <optional>
#include <vector>

namespace noya {

/// @brief Affine function k*x + b used by li_chao_tree.
template <class T> struct li_chao_line {
  T k{};
  T b{};

  T operator()(const T &x) const { return k * x + b; }
};

/// @brief Li Chao tree on a fixed set of coordinates.
template <class T, bool Min = true> struct li_chao_tree {
  using line_type = li_chao_line<T>;

  std::vector<T> xs;
  std::vector<std::optional<line_type>> seg;

  li_chao_tree() = default;
  explicit li_chao_tree(std::vector<T> poi) { build(std::move(poi)); }

  /// @brief Rebuild on a sorted unique copy of the query coordinates.
  void build(std::vector<T> poi) {
    std::sort(poi.begin(), poi.end());
    poi.erase(std::unique(poi.begin(), poi.end()), poi.end());
    xs = std::move(poi);
    seg.assign(std::max(1, int(xs.size()) * 4), std::nullopt);
  }

  /// @brief Add a line on every registered coordinate.
  void add_line(line_type ln) {
    if (!xs.empty()) {
      add_line_at(1, 0, int(xs.size()), ln);
    }
  }

  /// @brief Add a line on registered coordinates x in [l, r).
  void add_segment(const T &l, const T &r, line_type ln) {
    int bg = int(std::lower_bound(xs.begin(), xs.end(), l) - xs.begin());
    int end = int(std::lower_bound(xs.begin(), xs.end(), r) - xs.begin());
    if (bg < end) {
      add_segment_at(1, 0, int(xs.size()), bg, end, ln);
    }
  }

  /// @brief Query the optimum at a registered coordinate x.
  T query(const T &x) const {
    auto ans = query_optional(x);
    assert(ans.has_value());
    return *ans;
  }

  /// @brief Query the optimum at x, or nullopt when no active line covers x.
  std::optional<T> query_optional(const T &x) const {
    auto it = std::lower_bound(xs.begin(), xs.end(), x);
    assert(it != xs.end() && *it == x);
    return query_index_optional(int(it - xs.begin()));
  }

  /// @brief Query the optimum at xs[idx].
  T query_index(int idx) const {
    auto ans = query_index_optional(idx);
    assert(ans.has_value());
    return *ans;
  }

  /// @brief Query by coordinate index, or nullopt when it is uncovered.
  std::optional<T> query_index_optional(int idx) const {
    assert(0 <= idx && idx < int(xs.size()));
    std::optional<T> ans;
    query_at(1, 0, int(xs.size()), idx, ans);
    return ans;
  }

private:
  static bool better(const T &l, const T &r) {
    if constexpr (Min) {
      return l < r;
    } else {
      return r < l;
    }
  }

  void add_line_at(int nod, int l, int r, line_type ln) {
    if (!seg[nod]) {
      seg[nod] = ln;
      return;
    }
    int mid = (l + r) / 2;
    bool wl = better(ln(xs[l]), (*seg[nod])(xs[l]));
    bool wm = better(ln(xs[mid]), (*seg[nod])(xs[mid]));
    if (wm) {
      std::swap(ln, *seg[nod]);
    }
    if (r - l == 1) {
      return;
    }
    if (wl != wm) {
      add_line_at(nod * 2, l, mid, ln);
    } else {
      add_line_at(nod * 2 + 1, mid, r, ln);
    }
  }

  void add_segment_at(int nod, int l, int r, int ql, int qr, line_type ln) {
    if (qr <= l || r <= ql) {
      return;
    }
    if (ql <= l && r <= qr) {
      add_line_at(nod, l, r, ln);
      return;
    }
    int mid = (l + r) / 2;
    add_segment_at(nod * 2, l, mid, ql, qr, ln);
    add_segment_at(nod * 2 + 1, mid, r, ql, qr, ln);
  }

  void query_at(int nod, int l, int r, int idx, std::optional<T> &ans) const {
    if (seg[nod]) {
      T val = (*seg[nod])(xs[idx]);
      if (!ans || better(val, *ans)) {
        ans = val;
      }
    }
    if (r - l == 1) {
      return;
    }
    int mid = (l + r) / 2;
    if (idx < mid) {
      query_at(nod * 2, l, mid, idx, ans);
    } else {
      query_at(nod * 2 + 1, mid, r, idx, ans);
    }
  }
};

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

/// @complexity Time: O(log n) per line/segment insertion or point query on n coordinates.
/// Space: O(n).

#include <algorithm>
#include <cassert>
#include <optional>
#include <vector>

namespace noya {

/// @brief Affine function k*x + b used by li_chao_tree.
template <class T> struct li_chao_line {
  T k{};
  T b{};

  T operator()(const T &x) const { return k * x + b; }
};

/// @brief Li Chao tree on a fixed set of coordinates.
template <class T, bool Min = true> struct li_chao_tree {
  using line_type = li_chao_line<T>;

  std::vector<T> xs;
  std::vector<std::optional<line_type>> seg;

  li_chao_tree() = default;
  explicit li_chao_tree(std::vector<T> poi) { build(std::move(poi)); }

  /// @brief Rebuild on a sorted unique copy of the query coordinates.
  void build(std::vector<T> poi) {
    std::sort(poi.begin(), poi.end());
    poi.erase(std::unique(poi.begin(), poi.end()), poi.end());
    xs = std::move(poi);
    seg.assign(std::max(1, int(xs.size()) * 4), std::nullopt);
  }

  /// @brief Add a line on every registered coordinate.
  void add_line(line_type ln) {
    if (!xs.empty()) {
      add_line_at(1, 0, int(xs.size()), ln);
    }
  }

  /// @brief Add a line on registered coordinates x in [l, r).
  void add_segment(const T &l, const T &r, line_type ln) {
    int bg = int(std::lower_bound(xs.begin(), xs.end(), l) - xs.begin());
    int end = int(std::lower_bound(xs.begin(), xs.end(), r) - xs.begin());
    if (bg < end) {
      add_segment_at(1, 0, int(xs.size()), bg, end, ln);
    }
  }

  /// @brief Query the optimum at a registered coordinate x.
  T query(const T &x) const {
    auto ans = query_optional(x);
    assert(ans.has_value());
    return *ans;
  }

  /// @brief Query the optimum at x, or nullopt when no active line covers x.
  std::optional<T> query_optional(const T &x) const {
    auto it = std::lower_bound(xs.begin(), xs.end(), x);
    assert(it != xs.end() && *it == x);
    return query_index_optional(int(it - xs.begin()));
  }

  /// @brief Query the optimum at xs[idx].
  T query_index(int idx) const {
    auto ans = query_index_optional(idx);
    assert(ans.has_value());
    return *ans;
  }

  /// @brief Query by coordinate index, or nullopt when it is uncovered.
  std::optional<T> query_index_optional(int idx) const {
    assert(0 <= idx && idx < int(xs.size()));
    std::optional<T> ans;
    query_at(1, 0, int(xs.size()), idx, ans);
    return ans;
  }

private:
  static bool better(const T &l, const T &r) {
    if constexpr (Min) {
      return l < r;
    } else {
      return r < l;
    }
  }

  void add_line_at(int nod, int l, int r, line_type ln) {
    if (!seg[nod]) {
      seg[nod] = ln;
      return;
    }
    int mid = (l + r) / 2;
    bool wl = better(ln(xs[l]), (*seg[nod])(xs[l]));
    bool wm = better(ln(xs[mid]), (*seg[nod])(xs[mid]));
    if (wm) {
      std::swap(ln, *seg[nod]);
    }
    if (r - l == 1) {
      return;
    }
    if (wl != wm) {
      add_line_at(nod * 2, l, mid, ln);
    } else {
      add_line_at(nod * 2 + 1, mid, r, ln);
    }
  }

  void add_segment_at(int nod, int l, int r, int ql, int qr, line_type ln) {
    if (qr <= l || r <= ql) {
      return;
    }
    if (ql <= l && r <= qr) {
      add_line_at(nod, l, r, ln);
      return;
    }
    int mid = (l + r) / 2;
    add_segment_at(nod * 2, l, mid, ql, qr, ln);
    add_segment_at(nod * 2 + 1, mid, r, ql, qr, ln);
  }

  void query_at(int nod, int l, int r, int idx, std::optional<T> &ans) const {
    if (seg[nod]) {
      T val = (*seg[nod])(xs[idx]);
      if (!ans || better(val, *ans)) {
        ans = val;
      }
    }
    if (r - l == 1) {
      return;
    }
    int mid = (l + r) / 2;
    if (idx < mid) {
      query_at(nod * 2, l, mid, idx, ans);
    } else {
      query_at(nod * 2 + 1, mid, r, idx, ans);
    }
  }
};

} // namespace noya

#endif // NOYA_LI_CHAO_TREE_HPP
#include <algorithm>
#include <cassert>
#include <optional>
#include <vector>

/// @complexity Time: O(log n) per line/segment insertion or point query on n coordinates.
/// Space: O(n).

namespace noya {

/// @brief Affine function k*x + b used by li_chao_tree.
template <class T> struct li_chao_line {
  T k{};
  T b{};

  T operator()(const T &x) const { return k * x + b; }
};

/// @brief Li Chao tree on a fixed set of coordinates.
template <class T, bool Min = true> struct li_chao_tree {
  using line_type = li_chao_line<T>;

  std::vector<T> xs;
  std::vector<std::optional<line_type>> seg;

  li_chao_tree() = default;
  explicit li_chao_tree(std::vector<T> poi) { build(std::move(poi)); }

  /// @brief Rebuild on a sorted unique copy of the query coordinates.
  void build(std::vector<T> poi) {
    std::sort(poi.begin(), poi.end());
    poi.erase(std::unique(poi.begin(), poi.end()), poi.end());
    xs = std::move(poi);
    seg.assign(std::max(1, int(xs.size()) * 4), std::nullopt);
  }

  /// @brief Add a line on every registered coordinate.
  void add_line(line_type ln) {
    if (!xs.empty()) {
      add_line_at(1, 0, int(xs.size()), ln);
    }
  }

  /// @brief Add a line on registered coordinates x in [l, r).
  void add_segment(const T &l, const T &r, line_type ln) {
    int bg = int(std::lower_bound(xs.begin(), xs.end(), l) - xs.begin());
    int end = int(std::lower_bound(xs.begin(), xs.end(), r) - xs.begin());
    if (bg < end) {
      add_segment_at(1, 0, int(xs.size()), bg, end, ln);
    }
  }

  /// @brief Query the optimum at a registered coordinate x.
  T query(const T &x) const {
    auto ans = query_optional(x);
    assert(ans.has_value());
    return *ans;
  }

  /// @brief Query the optimum at x, or nullopt when no active line covers x.
  std::optional<T> query_optional(const T &x) const {
    auto it = std::lower_bound(xs.begin(), xs.end(), x);
    assert(it != xs.end() && *it == x);
    return query_index_optional(int(it - xs.begin()));
  }

  /// @brief Query the optimum at xs[idx].
  T query_index(int idx) const {
    auto ans = query_index_optional(idx);
    assert(ans.has_value());
    return *ans;
  }

  /// @brief Query by coordinate index, or nullopt when it is uncovered.
  std::optional<T> query_index_optional(int idx) const {
    assert(0 <= idx && idx < int(xs.size()));
    std::optional<T> ans;
    query_at(1, 0, int(xs.size()), idx, ans);
    return ans;
  }

private:
  static bool better(const T &l, const T &r) {
    if constexpr (Min) {
      return l < r;
    } else {
      return r < l;
    }
  }

  void add_line_at(int nod, int l, int r, line_type ln) {
    if (!seg[nod]) {
      seg[nod] = ln;
      return;
    }
    int mid = (l + r) / 2;
    bool wl = better(ln(xs[l]), (*seg[nod])(xs[l]));
    bool wm = better(ln(xs[mid]), (*seg[nod])(xs[mid]));
    if (wm) {
      std::swap(ln, *seg[nod]);
    }
    if (r - l == 1) {
      return;
    }
    if (wl != wm) {
      add_line_at(nod * 2, l, mid, ln);
    } else {
      add_line_at(nod * 2 + 1, mid, r, ln);
    }
  }

  void add_segment_at(int nod, int l, int r, int ql, int qr, line_type ln) {
    if (qr <= l || r <= ql) {
      return;
    }
    if (ql <= l && r <= qr) {
      add_line_at(nod, l, r, ln);
      return;
    }
    int mid = (l + r) / 2;
    add_segment_at(nod * 2, l, mid, ql, qr, ln);
    add_segment_at(nod * 2 + 1, mid, r, ql, qr, ln);
  }

  void query_at(int nod, int l, int r, int idx, std::optional<T> &ans) const {
    if (seg[nod]) {
      T val = (*seg[nod])(xs[idx]);
      if (!ans || better(val, *ans)) {
        ans = val;
      }
    }
    if (r - l == 1) {
      return;
    }
    int mid = (l + r) / 2;
    if (idx < mid) {
      query_at(nod * 2, l, mid, idx, ans);
    } else {
      query_at(nod * 2 + 1, mid, r, idx, ans);
    }
  }
};

} // namespace noya