Skip to content

rollback_mo.hpp

SECTIONData Structure INCLUDEnoya/ds/rollback_mo.hpp

以回滚结构处理离线区间询问,避免删除操作;适合只能添加元素、但状态可以整体撤销的答案维护。

Complexity: Time: \(O(Q \log (Q + 1))\) sorting and \(O(N \sqrt{Q} + Q)\) add/undo steps. Callback costs apply to these steps. Space: \(O(Q)\) scheduler storage.

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(Q log(Q+1)) sorting and O(N sqrt(Q) + Q) add/undo steps.
/// Callback costs apply to these steps. Space: O(Q) scheduler storage.

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

namespace noya {

/// @brief Offline half-open range scheduler for rollbackable add-only states.
/// Each query uses add(position), snapshot(), rollback(token), and answer(id).
/// Answers must depend on the included positions, not their insertion order.
/// The caller's initial state is restored before run() returns.
struct rollback_mo {
  int n = 0;
  std::vector<std::pair<int, int>> qs;

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

  int add_query(int l, int r) {
    assert(0 <= l && l <= r && r <= n);
    int id = int(qs.size());
    qs.emplace_back(l, r);
    return id;
  }

  template <class Add, class Snapshot, class Rollback, class Answer>
  void run(Add add, Snapshot ss, Rollback rb, Answer ans) const {
    if (qs.empty()) {
      return;
    }
    int left = n, right = 0, last_left = 0, count = 0;
    for (auto [l, r] : qs) {
      if (l < r) {
        left = std::min(left, l);
        right = std::max(right, r);
        last_left = std::max(last_left, l);
        ++count;
      }
    }
    int span = count == 0 ? 0 : right - left;
    int left_span = count == 0 ? 0 : last_left - left;
    // Balance persistent right scans against temporary left additions.
    int bs = std::max(1, int(std::sqrt(double(span) * left_span /
                                     std::max(1, count))));
    int nb = left_span / bs + 1;
    std::vector<std::vector<int>> gs(nb);
    auto a0 = ss();

    for (int id = 0; id < int(qs.size()); id++) {
      auto [l, r] = qs[id];
      // Empty queries may lie outside the span of all nonempty queries.
      int blk = l == r ? 0 : (l - left) / bs;
      int br = l == r
                   ? r
                   : left + int(std::min<long long>(span, (blk + 1LL) * bs));
      if (r <= br) {
        auto tok = ss();
        for (int pos = l; pos < r; pos++) {
          add(pos);
        }
        ans(id);
        rb(tok);
      } else {
        gs[blk].push_back(id);
      }
    }

    for (int blk = 0; blk < nb; blk++) {
      auto &ord = gs[blk];
      if (ord.empty()) {
        continue;
      }
      std::sort(ord.begin(), ord.end(), [&](int a, int b) {
        return qs[a].second < qs[b].second;
      });
      // Start the shared state at this group's largest left endpoint.
      int base = 0;
      for (int id : ord) base = std::max(base, qs[id].first);
      int r = base;
      for (int id : ord) {
        auto [ql, qr] = qs[id];
        while (r < qr) {
          add(r++);
        }
        auto tok = ss();
        for (int pos = base - 1; pos >= ql; pos--) {
          add(pos);
        }
        ans(id);
        rb(tok);
      }
      rb(a0);
    }
  }
};

} // namespace noya

 // NOYA_ROLLBACK_MO_HPP
#ifndef NOYA_ROLLBACK_MO_HPP
#define NOYA_ROLLBACK_MO_HPP 1

/// @complexity Time: O(Q log(Q+1)) sorting and O(N sqrt(Q) + Q) add/undo steps.
/// Callback costs apply to these steps. Space: O(Q) scheduler storage.

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

namespace noya {

/// @brief Offline half-open range scheduler for rollbackable add-only states.
/// Each query uses add(position), snapshot(), rollback(token), and answer(id).
/// Answers must depend on the included positions, not their insertion order.
/// The caller's initial state is restored before run() returns.
struct rollback_mo {
  int n = 0;
  std::vector<std::pair<int, int>> qs;

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

  int add_query(int l, int r) {
    assert(0 <= l && l <= r && r <= n);
    int id = int(qs.size());
    qs.emplace_back(l, r);
    return id;
  }

  template <class Add, class Snapshot, class Rollback, class Answer>
  void run(Add add, Snapshot ss, Rollback rb, Answer ans) const {
    if (qs.empty()) {
      return;
    }
    int left = n, right = 0, last_left = 0, count = 0;
    for (auto [l, r] : qs) {
      if (l < r) {
        left = std::min(left, l);
        right = std::max(right, r);
        last_left = std::max(last_left, l);
        ++count;
      }
    }
    int span = count == 0 ? 0 : right - left;
    int left_span = count == 0 ? 0 : last_left - left;
    // Balance persistent right scans against temporary left additions.
    int bs = std::max(1, int(std::sqrt(double(span) * left_span /
                                     std::max(1, count))));
    int nb = left_span / bs + 1;
    std::vector<std::vector<int>> gs(nb);
    auto a0 = ss();

    for (int id = 0; id < int(qs.size()); id++) {
      auto [l, r] = qs[id];
      // Empty queries may lie outside the span of all nonempty queries.
      int blk = l == r ? 0 : (l - left) / bs;
      int br = l == r
                   ? r
                   : left + int(std::min<long long>(span, (blk + 1LL) * bs));
      if (r <= br) {
        auto tok = ss();
        for (int pos = l; pos < r; pos++) {
          add(pos);
        }
        ans(id);
        rb(tok);
      } else {
        gs[blk].push_back(id);
      }
    }

    for (int blk = 0; blk < nb; blk++) {
      auto &ord = gs[blk];
      if (ord.empty()) {
        continue;
      }
      std::sort(ord.begin(), ord.end(), [&](int a, int b) {
        return qs[a].second < qs[b].second;
      });
      // Start the shared state at this group's largest left endpoint.
      int base = 0;
      for (int id : ord) base = std::max(base, qs[id].first);
      int r = base;
      for (int id : ord) {
        auto [ql, qr] = qs[id];
        while (r < qr) {
          add(r++);
        }
        auto tok = ss();
        for (int pos = base - 1; pos >= ql; pos--) {
          add(pos);
        }
        ans(id);
        rb(tok);
      }
      rb(a0);
    }
  }
};

} // namespace noya

#endif // NOYA_ROLLBACK_MO_HPP
/// @complexity Time: O(Q log(Q+1)) sorting and O(N sqrt(Q) + Q) add/undo steps.
/// Callback costs apply to these steps. Space: O(Q) scheduler storage.

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

namespace noya {

/// @brief Offline half-open range scheduler for rollbackable add-only states.
/// Each query uses add(position), snapshot(), rollback(token), and answer(id).
/// Answers must depend on the included positions, not their insertion order.
/// The caller's initial state is restored before run() returns.
struct rollback_mo {
  int n = 0;
  std::vector<std::pair<int, int>> qs;

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

  int add_query(int l, int r) {
    assert(0 <= l && l <= r && r <= n);
    int id = int(qs.size());
    qs.emplace_back(l, r);
    return id;
  }

  template <class Add, class Snapshot, class Rollback, class Answer>
  void run(Add add, Snapshot ss, Rollback rb, Answer ans) const {
    if (qs.empty()) {
      return;
    }
    int left = n, right = 0, last_left = 0, count = 0;
    for (auto [l, r] : qs) {
      if (l < r) {
        left = std::min(left, l);
        right = std::max(right, r);
        last_left = std::max(last_left, l);
        ++count;
      }
    }
    int span = count == 0 ? 0 : right - left;
    int left_span = count == 0 ? 0 : last_left - left;
    // Balance persistent right scans against temporary left additions.
    int bs = std::max(1, int(std::sqrt(double(span) * left_span /
                                     std::max(1, count))));
    int nb = left_span / bs + 1;
    std::vector<std::vector<int>> gs(nb);
    auto a0 = ss();

    for (int id = 0; id < int(qs.size()); id++) {
      auto [l, r] = qs[id];
      // Empty queries may lie outside the span of all nonempty queries.
      int blk = l == r ? 0 : (l - left) / bs;
      int br = l == r
                   ? r
                   : left + int(std::min<long long>(span, (blk + 1LL) * bs));
      if (r <= br) {
        auto tok = ss();
        for (int pos = l; pos < r; pos++) {
          add(pos);
        }
        ans(id);
        rb(tok);
      } else {
        gs[blk].push_back(id);
      }
    }

    for (int blk = 0; blk < nb; blk++) {
      auto &ord = gs[blk];
      if (ord.empty()) {
        continue;
      }
      std::sort(ord.begin(), ord.end(), [&](int a, int b) {
        return qs[a].second < qs[b].second;
      });
      // Start the shared state at this group's largest left endpoint.
      int base = 0;
      for (int id : ord) base = std::max(base, qs[id].first);
      int r = base;
      for (int id : ord) {
        auto [ql, qr] = qs[id];
        while (r < qr) {
          add(r++);
        }
        auto tok = ss();
        for (int pos = base - 1; pos >= ql; pos--) {
          add(pos);
        }
        ans(id);
        rb(tok);
      }
      rb(a0);
    }
  }
};

} // namespace noya

 // NOYA_ROLLBACK_MO_HPP