Skip to content

li_chao_tree.hpp

SECTIONOptimization INCLUDEnoya/li_chao_tree.hpp

Affine function slope*x + intercept used by li_chao_tree.

Verified by line_add_get_min, segment_add_get_min.

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

Implementation

View on GitHub

#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(number of stored tree nodes).

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

namespace noya {

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

  T operator()(const T &x) const { return slope * x + intercept; }
};

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

  std::vector<T> coordinates;
  std::vector<std::optional<line_type>> lines;

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

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

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

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

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

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

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

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

private:
  static bool better(const T &left, const T &right) {
    if constexpr (Minimize) {
      return left < right;
    } else {
      return right < left;
    }
  }

  void add_line_at(int node, int left, int right, line_type line) {
    if (!lines[node]) {
      lines[node] = line;
      return;
    }
    int middle = (left + right) / 2;
    bool wins_left =
        better(line(coordinates[left]), (*lines[node])(coordinates[left]));
    bool wins_middle =
        better(line(coordinates[middle]), (*lines[node])(coordinates[middle]));
    if (wins_middle) {
      std::swap(line, *lines[node]);
    }
    if (right - left == 1) {
      return;
    }
    if (wins_left != wins_middle) {
      add_line_at(node * 2, left, middle, line);
    } else {
      add_line_at(node * 2 + 1, middle, right, line);
    }
  }

  void add_segment_at(int node, int left, int right, int query_left,
                      int query_right, line_type line) {
    if (query_right <= left || right <= query_left) {
      return;
    }
    if (query_left <= left && right <= query_right) {
      add_line_at(node, left, right, line);
      return;
    }
    int middle = (left + right) / 2;
    add_segment_at(node * 2, left, middle, query_left, query_right, line);
    add_segment_at(node * 2 + 1, middle, right, query_left, query_right, line);
  }

  void query_at(int node, int left, int right, int index,
                std::optional<T> &answer) const {
    if (lines[node]) {
      T value = (*lines[node])(coordinates[index]);
      if (!answer || better(value, *answer)) {
        answer = value;
      }
    }
    if (right - left == 1) {
      return;
    }
    int middle = (left + right) / 2;
    if (index < middle) {
      query_at(node * 2, left, middle, index, answer);
    } else {
      query_at(node * 2 + 1, middle, right, index, answer);
    }
  }
};

} // 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(number of stored tree nodes).

namespace noya {

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

  T operator()(const T &x) const { return slope * x + intercept; }
};

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

  std::vector<T> coordinates;
  std::vector<std::optional<line_type>> lines;

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

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

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

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

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

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

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

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

private:
  static bool better(const T &left, const T &right) {
    if constexpr (Minimize) {
      return left < right;
    } else {
      return right < left;
    }
  }

  void add_line_at(int node, int left, int right, line_type line) {
    if (!lines[node]) {
      lines[node] = line;
      return;
    }
    int middle = (left + right) / 2;
    bool wins_left =
        better(line(coordinates[left]), (*lines[node])(coordinates[left]));
    bool wins_middle =
        better(line(coordinates[middle]), (*lines[node])(coordinates[middle]));
    if (wins_middle) {
      std::swap(line, *lines[node]);
    }
    if (right - left == 1) {
      return;
    }
    if (wins_left != wins_middle) {
      add_line_at(node * 2, left, middle, line);
    } else {
      add_line_at(node * 2 + 1, middle, right, line);
    }
  }

  void add_segment_at(int node, int left, int right, int query_left,
                      int query_right, line_type line) {
    if (query_right <= left || right <= query_left) {
      return;
    }
    if (query_left <= left && right <= query_right) {
      add_line_at(node, left, right, line);
      return;
    }
    int middle = (left + right) / 2;
    add_segment_at(node * 2, left, middle, query_left, query_right, line);
    add_segment_at(node * 2 + 1, middle, right, query_left, query_right, line);
  }

  void query_at(int node, int left, int right, int index,
                std::optional<T> &answer) const {
    if (lines[node]) {
      T value = (*lines[node])(coordinates[index]);
      if (!answer || better(value, *answer)) {
        answer = value;
      }
    }
    if (right - left == 1) {
      return;
    }
    int middle = (left + right) / 2;
    if (index < middle) {
      query_at(node * 2, left, middle, index, answer);
    } else {
      query_at(node * 2 + 1, middle, right, index, answer);
    }
  }
};

} // namespace noya