m1une's library

This documentation is automatically generated by online-judge-tools/verification-helper

View on GitHub

:heavy_check_mark: Mo's Algorithm
(algo/offline/mo.hpp)

Overview

Mo reorders static range queries so a maintained half-open interval changes only a small number of positions between consecutive queries.

Use it when:

Basic API

m1une::algo::Mo mo(n);
int id = mo.add_query(left, right);
mo.run(add, remove, answer);

Queries use zero-based half-open intervals [left, right). add_query returns an insertion-order query ID. During run, answer(query_id) is called when the maintained state represents exactly that query’s range.

For statistics whose update is the same on both sides, use:

mo.run(add, remove, answer);

Both add(index) and remove(index) receive an array index.

Directional Updates

Some statistics depend on which endpoint moves. Inversion counting is a common example: adding an element to the left counts smaller existing elements, while adding one to the right counts larger existing elements.

Use the full overload:

mo.run(
    add_left,
    add_right,
    remove_left,
    remove_right,
    answer
);

Every callback receives the index being inserted or erased.

Methods

Method Description Complexity
Mo(int n) Creates a query collection for an array of length n. $O(1)$
add_query(l, r) Adds [l, r) and returns its query ID. Amortized $O(1)$
query_count() Returns the number of queries. $O(1)$
queries() Returns all queries in insertion order. $O(1)$
order(block_size) Returns query IDs in processing order. $O(Q\log Q)$
run(...) Processes all queries. See below
reserve(q) Reserves storage for q queries. $O(Q)$
clear() Removes all queries while retaining array length. $O(Q)$

order and run choose a block size automatically when block_size <= 0. Passing a positive value allows problem-specific tuning. Right endpoints are sorted in alternating directions between adjacent left blocks.

With $O(F)$ insertion and deletion callbacks, the usual complexity is $O((N+Q)\sqrt{Q}\,F + Q\log Q)$ using the automatically selected block size. The exact number of endpoint movements depends on the query distribution.

Example

This computes the number of distinct values in every query:

#include "algo/offline/mo.hpp"

#include <vector>

int main() {
    std::vector<int> values = {1, 2, 1, 3};
    m1une::algo::Mo mo(int(values.size()));
    mo.add_query(0, 3);
    mo.add_query(1, 4);

    std::vector<int> frequency(4);
    std::vector<int> result(mo.query_count());
    int distinct = 0;

    mo.run(
        [&](int index) {
            if (frequency[values[index]]++ == 0) distinct++;
        },
        [&](int index) {
            if (--frequency[values[index]] == 0) distinct--;
        },
        [&](int query_id) {
            result[query_id] = distinct;
        }
    );
}

Required by

Verified with

Code

#ifndef M1UNE_ALGO_OFFLINE_MO_HPP
#define M1UNE_ALGO_OFFLINE_MO_HPP 1

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

namespace m1une {
namespace algo {

// Offline Mo's algorithm for half-open array ranges.
struct Mo {
    struct Query {
        int left;
        int right;
        int id;
    };

   private:
    int _n;
    std::vector<Query> _queries;

   public:
    Mo() : _n(0) {}

    explicit Mo(int n) : _n(n) {
        assert(0 <= n);
    }

    int size() const {
        return _n;
    }

    int query_count() const {
        return int(_queries.size());
    }

    bool empty() const {
        return _queries.empty();
    }

    const std::vector<Query>& queries() const {
        return _queries;
    }

    void reserve(int query_capacity) {
        assert(0 <= query_capacity);
        _queries.reserve(query_capacity);
    }

    void clear() {
        _queries.clear();
    }

    // Adds [left, right) and returns its insertion-order ID.
    int add_query(int left, int right) {
        assert(0 <= left && left <= right && right <= _n);
        int id = query_count();
        _queries.push_back(Query{left, right, id});
        return id;
    }

    // Returns query IDs in Mo order. A non-positive block size selects one
    // automatically.
    std::vector<int> order(int block_size = 0) const {
        int query_size = query_count();
        std::vector<int> result(query_size);
        std::iota(result.begin(), result.end(), 0);
        if (query_size == 0) return result;

        if (block_size <= 0) {
            block_size = std::max(1, int(_n / std::sqrt(static_cast<double>(query_size))));
        }

        std::sort(result.begin(), result.end(), [&](int first, int second) {
            const Query& a = _queries[first];
            const Query& b = _queries[second];
            int first_block = a.left / block_size;
            int second_block = b.left / block_size;
            if (first_block != second_block) {
                return first_block < second_block;
            }
            if (first_block & 1) return a.right > b.right;
            return a.right < b.right;
        });
        return result;
    }

    // Maintains [left, right). Each movement callback receives the array index
    // being inserted or erased. `answer(query_id)` stores or reports a result.
    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,
             int block_size = 0) const {
        int left = 0;
        int right = 0;
        for (int query_index : order(block_size)) {
            const Query& query = _queries[query_index];
            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(query.id);
        }
    }

    // Convenience overload for statistics whose update is independent of
    // which side moves.
    template <class Add, class Remove, class Answer>
    void run(Add add, Remove remove, Answer answer, int block_size = 0) const {
        run(add, add, remove, remove, answer, block_size);
    }
};

}  // namespace algo
}  // namespace m1une

#endif  // M1UNE_ALGO_OFFLINE_MO_HPP
#line 1 "algo/offline/mo.hpp"



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

namespace m1une {
namespace algo {

// Offline Mo's algorithm for half-open array ranges.
struct Mo {
    struct Query {
        int left;
        int right;
        int id;
    };

   private:
    int _n;
    std::vector<Query> _queries;

   public:
    Mo() : _n(0) {}

    explicit Mo(int n) : _n(n) {
        assert(0 <= n);
    }

    int size() const {
        return _n;
    }

    int query_count() const {
        return int(_queries.size());
    }

    bool empty() const {
        return _queries.empty();
    }

    const std::vector<Query>& queries() const {
        return _queries;
    }

    void reserve(int query_capacity) {
        assert(0 <= query_capacity);
        _queries.reserve(query_capacity);
    }

    void clear() {
        _queries.clear();
    }

    // Adds [left, right) and returns its insertion-order ID.
    int add_query(int left, int right) {
        assert(0 <= left && left <= right && right <= _n);
        int id = query_count();
        _queries.push_back(Query{left, right, id});
        return id;
    }

    // Returns query IDs in Mo order. A non-positive block size selects one
    // automatically.
    std::vector<int> order(int block_size = 0) const {
        int query_size = query_count();
        std::vector<int> result(query_size);
        std::iota(result.begin(), result.end(), 0);
        if (query_size == 0) return result;

        if (block_size <= 0) {
            block_size = std::max(1, int(_n / std::sqrt(static_cast<double>(query_size))));
        }

        std::sort(result.begin(), result.end(), [&](int first, int second) {
            const Query& a = _queries[first];
            const Query& b = _queries[second];
            int first_block = a.left / block_size;
            int second_block = b.left / block_size;
            if (first_block != second_block) {
                return first_block < second_block;
            }
            if (first_block & 1) return a.right > b.right;
            return a.right < b.right;
        });
        return result;
    }

    // Maintains [left, right). Each movement callback receives the array index
    // being inserted or erased. `answer(query_id)` stores or reports a result.
    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,
             int block_size = 0) const {
        int left = 0;
        int right = 0;
        for (int query_index : order(block_size)) {
            const Query& query = _queries[query_index];
            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(query.id);
        }
    }

    // Convenience overload for statistics whose update is independent of
    // which side moves.
    template <class Add, class Remove, class Answer>
    void run(Add add, Remove remove, Answer answer, int block_size = 0) const {
        run(add, add, remove, remove, answer, block_size);
    }
};

}  // namespace algo
}  // namespace m1une
Back to top page