Skip to content

mo.hpp

SECTIONData Structure INCLUDEnoya/mo.hpp

Mo's algorithm for offline half-open range queries.

把静态区间询问重排,使左右端点移动总量较小;适合能在增删一个元素时维护答案的离线查询。

Implementation

View on GitHub

#ifndef NOYA_MO_HPP
#define NOYA_MO_HPP 1

/// @complexity Time: O((N + Q) sqrt(N)) add/remove steps with standard block ordering.
/// Space: O(Q).

#include <algorithm>
#include <cassert>
#include <cmath>
#include <numeric>
#include <utility>
#include <vector>

namespace noya {

/// @brief Mo's algorithm for offline half-open range queries.
struct mo {
  int n = 0;
  std::vector<std::pair<int, int>> queries;

  mo() = default;
  explicit mo(int n_) : n(n_) { assert(n >= 0); }

  /// @brief Append [left, right) and return its query id.
  int add_query(int left, int right) {
    assert(0 <= left && left <= right && right <= n);
    int id = int(queries.size());
    queries.emplace_back(left, right);
    return id;
  }

  /// @brief Move one shared window through all queries and call answer(id)
  /// after the corresponding range is active.
  template <class AddLeft, class AddRight, class RemoveLeft, class RemoveRight,
            class Answer>
  void run(AddLeft add_left, AddRight add_right, RemoveLeft remove_left,
           RemoveRight remove_right, Answer answer) const {
    int query_count = int(queries.size());
    if (query_count == 0) {
      return;
    }
    int block_size =
        std::max(1, int(n / std::max(1.0, std::sqrt(double(query_count)))));
    std::vector<int> order(query_count);
    std::iota(order.begin(), order.end(), 0);
    std::sort(order.begin(), order.end(), [&](int first, int second) {
      int first_block = queries[first].first / block_size;
      int second_block = queries[second].first / block_size;
      if (first_block != second_block) {
        return first_block < second_block;
      }
      if (first_block & 1) {
        return queries[first].second > queries[second].second;
      }
      return queries[first].second < queries[second].second;
    });

    int left = 0;
    int right = 0;
    for (int id : order) {
      auto [query_left, query_right] = queries[id];
      while (query_left < left) {
        add_left(--left);
      }
      while (right < query_right) {
        add_right(right++);
      }
      while (left < query_left) {
        remove_left(left++);
      }
      while (query_right < right) {
        remove_right(--right);
      }
      answer(id);
    }
  }
};

} // namespace noya

#endif // NOYA_MO_HPP
#include <algorithm>
#include <cassert>
#include <cmath>
#include <numeric>
#include <utility>
#include <vector>

/// @complexity Time: O((N + Q) sqrt(N)) add/remove steps with standard block ordering.
/// Space: O(Q).

namespace noya {

/// @brief Mo's algorithm for offline half-open range queries.
struct mo {
  int n = 0;
  std::vector<std::pair<int, int>> queries;

  mo() = default;
  explicit mo(int n_) : n(n_) { assert(n >= 0); }

  /// @brief Append [left, right) and return its query id.
  int add_query(int left, int right) {
    assert(0 <= left && left <= right && right <= n);
    int id = int(queries.size());
    queries.emplace_back(left, right);
    return id;
  }

  /// @brief Move one shared window through all queries and call answer(id)
  /// after the corresponding range is active.
  template <class AddLeft, class AddRight, class RemoveLeft, class RemoveRight,
            class Answer>
  void run(AddLeft add_left, AddRight add_right, RemoveLeft remove_left,
           RemoveRight remove_right, Answer answer) const {
    int query_count = int(queries.size());
    if (query_count == 0) {
      return;
    }
    int block_size =
        std::max(1, int(n / std::max(1.0, std::sqrt(double(query_count)))));
    std::vector<int> order(query_count);
    std::iota(order.begin(), order.end(), 0);
    std::sort(order.begin(), order.end(), [&](int first, int second) {
      int first_block = queries[first].first / block_size;
      int second_block = queries[second].first / block_size;
      if (first_block != second_block) {
        return first_block < second_block;
      }
      if (first_block & 1) {
        return queries[first].second > queries[second].second;
      }
      return queries[first].second < queries[second].second;
    });

    int left = 0;
    int right = 0;
    for (int id : order) {
      auto [query_left, query_right] = queries[id];
      while (query_left < left) {
        add_left(--left);
      }
      while (right < query_right) {
        add_right(right++);
      }
      while (left < query_left) {
        remove_left(left++);
      }
      while (query_right < right) {
        remove_right(--right);
      }
      answer(id);
    }
  }
};

} // namespace noya