Mo's algorithm

Formulation

Suppose n=mn=m. For range queries on a sequence, if the answer for [l,r][l,r] can be extended in O(1)O(1) time to the answers for [l−1,r],[l+1,r],[l,r+1],[l,r−1][l-1,r],[l+1,r],[l,r+1],[l,r-1] (the ranges adjacent to [l,r][l,r]), then all queries can be answered in O(nn)O(n\sqrt{n}) time.

Explanation

After sorting offline, process the queries in order and brute-force transfer the answer from the previous range to the next (moving one step at a time).

Sorting method

For a range [l,r][l,r], sort by the index of the block containing ll as the first key and by rr in ascending order as the second key.

Implementation

void move(int pos, int sign) {
  // update nowAns
}

void solve() {
  BLOCK_SIZE = int(ceil(pow(n, 0.5)));
  sort(querys, querys + m);
  for (int i = 0; i < m; ++i) {
    const query &q = querys[i];
    while (l > q.l) move(--l, 1);
    while (r < q.r) move(++r, 1);
    while (l < q.l) move(l++, -1);
    while (r > q.r) move(r--, -1);
    ans[q.id] = nowAns;
  }
}

Complexity analysis

The following discussion assumes that nn and mm are of the same order.

First comes block decomposition, whose time complexity is O(n⋅nlog⁡n+nlog⁡n)=O(nlog⁡n)O(\sqrt{n}\cdot\sqrt{n}\log\sqrt{n}+n\log n)=O(n\log n).

Next comes the essence of Mo's algorithm. We use an elementary proof below to show that its time complexity is O(nn)O(n\sqrt{n}).

Proof

Proof: Let the maximum value of LL in each block be max⁡1,max⁡2,max⁡3,⋯ ,max⁡⌈n⌉\max_1,\max_2,\max_3, \cdots , \max_{\lceil\sqrt{n}\rceil}.

The first sort gives max⁡1≤max⁡2≤⋯≤max⁡⌈n⌉\max_1 \le \max_2 \le \cdots \le \max_{\lceil\sqrt{n}\rceil}.

Clearly, brute-forcing the first query in each block takes O(n)O(n) time.

Consider the worst case: in every block, the maximum value of RR is nn, and each update changes LL from max⁡i−1\max_{i - 1} to max⁡i\max_i or from max⁡i\max_i to max⁡i−1\max_{i - 1}.

Consider RR. Since RR is already sorted within a block, updating it within one block takes O(n)O(n) time, and takes O(nn)O(n\sqrt{n}) over all blocks.

The key is LL: each change takes O(max⁡i−max⁡i−1)O(\max_i-\max_{i-1}) time, so the time complexity within one block is O(n⋅(max⁡i−max⁡i−1))O(\sqrt{n}\cdot(\max_i-\max_{i-1})).

Combining the time complexities for LL over all blocks gives:

The total time complexity for LL is

O(n(max⁡1−1)+n(max⁡2−max⁡1)+n(max⁡3−max⁡2)+⋯+n(max⁡⌈n⌉−max⁡⌈n⌉−1))=O(n⋅(max⁡1−1+max⁡2−max⁡1+max⁡3−max⁡2+⋯+max⁡⌈n⌉−1−max⁡⌈n⌉−2+max⁡⌈n⌉−max⁡⌈n⌉−1))=O(n⋅(max⁡⌈n⌉−1))\begin{aligned} & O(\sqrt{n}(\max{}_1-1)+\sqrt{n}(\max{}_2-\max{}_1)+\sqrt{n}(\max{}_3-\max{}_2)+\cdots+\sqrt{n}(\max{}_{\lceil\sqrt{n}\rceil}-\max{}_{\lceil\sqrt{n}\rceil-1))} \\ = \phantom{} & O(\sqrt{n}\cdot(\max{}_1-1+\max{}_2-\max{}_1+\max{}_3-\max{}_2+\cdots+\max{}_{\lceil\sqrt{n}\rceil-1}-\max{}_{\lceil\sqrt{n}\rceil-2}+\max{}_{\lceil\sqrt{n}\rceil}-\max{}_{\lceil\sqrt{n}\rceil-1)}) \\ = \phantom{} & O(\sqrt{n}\cdot(\max{}_{\lceil\sqrt{n}\rceil}-1))\\ \end{aligned}

(telescoping sum)

By the problem statement max⁡⌈n⌉\max_{\lceil\sqrt{n}\rceil} is at most nn,so the worst-case total time complexity for LL is O(nn)O(n\sqrt{n}).

Therefore, the time complexity of Mo's algorithm is O(nn)O(n\sqrt{n}).

For other values of mm, such as m<nm<n, the block decomposition must be changed for better performance.

How should we choose the blocks?

Let the block length be SS. For any number of queries in the same block, the movement distance is nn. There are nS\displaystyle \frac{n}{S} blocks, so the total movement is n2S\displaystyle \frac{n^2}{S}. Since movement may cross block boundaries, we must also add a complexity of mSmS. Thus, the total complexity is O(n2S+mS)\displaystyle O\left(\frac{n^2}{S}+mS\right). To minimize this value, the two terms should be made as equal as possible. We find that choosing SS as nm\displaystyle \frac{n}{\sqrt{m}} is optimal, giving a complexity of O(n2nm+m(nm))=O(nm)\displaystyle O\left(\frac{n^2}{\displaystyle \frac{n}{\sqrt{m}}}+m\left(\frac{n}{\sqrt{m}}\right)\right)=O(n\sqrt{m}).

In fact, an inaccurate block length can greatly affect Mo's time complexity. For example, if mm and n\sqrt n are of the same order but the block length is incorrectly set to n\sqrt n, it is easy to construct data with time complexity O(nn)O(n \sqrt n) instead of the correct O(n5/4)O(n^{5/4}).

Mo's algorithm looks very brute-force largely because its block-sorting method looks rough. One might try a more refined sort of all ranges. For example, regard every range [l,r][l, r] as a point (l,r)(l, r) in the plane, build a Manhattan minimum spanning tree on all points, and transfer answers between queries along its edges. This seems to improve the time complexity, but the upper bound of block-sorting the queries is already optimal.

Suppose n,mn, m are of the same order and nn is a perfect square. Consider ranges of the form [an,bn](1≤a,b≤n)[a \sqrt n, b \sqrt n](1 \le a, b \le \sqrt n); there are nn such ranges. If all ranges are viewed as points in the plane, the Manhattan distance between two points is exactly the cost of transferring between the two ranges, and the minimum Manhattan distance between any two ranges is n\sqrt n. Thus the minimum time complexity for processing all queries is O(nn)O(n \sqrt n). Other cases can be constructed similarly.

Mo's algorithm has another feature: with nn fixed, the larger mm is, the smaller the average transfer cost per query. Some other offline algorithms have the same feature (such as Tarjan's algorithm for finding LCAs), but Mo's algorithm shows the most pronounced change in average transfer cost as mm varies.

Example & code

Example: [National Training Team] Little Z's Socks

Problem statement:

Given a sequence of length nn with elements {ci}\{c_i\} and mm queries, each supplying l,rl,r, choose two distinct numbers uniformly at random from the elements whose indices range from ll through rr, and find the probability that they are equal.

Process

Idea: this is a template problem for Mo's algorithm.

For a range [l,r][l,r], sort by the index of the block containing ll as the first key and by rr in ascending order as the second key.

Start calculating from the first query. Compute the first query by brute force in O(n)O(n) time, then obtain each later answer from the previous query.

Details:

For the range [i,i][i,i], the answer is easy because it contains only one element. Then move step by step from the current range, whose answer is known, toward the next range.

Let col[i]col[i] denote the number of occurrences of color ii in the current range, and let ansans denote the number of valid pairs (the number of ways to choose two socks of the same color). On each move, update the answer: let the current color be kk; when extending the range, add to ansans the value (col[k]+12)−(col[k]2)\dbinom{col[k]+1}{2}-\dbinom{col[k]}{2}; when shrinking, subtract from ansans the value (col[k]2)−(col[k]−12)\dbinom{col[k]}{2}-\dbinom{col[k]-1}{2}. The answer to this query is ans(r−l+12)\displaystyle \frac{ans}{\dbinom{r-l+1}{2}}.

There is a simplification:(a2)=a(a−1)2\displaystyle \dbinom{a}{2}=\frac{a (a-1)}{2}.

Thus (a+12)−(a2)=(a+1)a2−a(a−1)2=a2⋅(a+1−a+1)=a2⋅2=a\displaystyle \dbinom{a+1}{2}-\dbinom{a}{2}=\frac{(a+1) a}{2}-\frac{a (a-1)}{2}=\frac{a}{2}\cdot (a+1-a+1)=\frac{a}{2}\cdot 2=a.

Thus (col[k]+12)−(col[k]2)=col[k]\dbinom{col[k]+1}{2}-\dbinom{col[k]}{2}=col[k].

Total complexity: O(nn)O(n\sqrt{n} )

In the code below, deno is the denominator, nume is the numerator, sqn is the block size n\sqrt{n}, arr is the input array, node is the query structure, tab is the sorted query sequence, and col is as defined above.

Note: because of ++l and --r, the positions of the four while loops that move the range in the following code are crucial; their relative order must not be changed arbitrarily.

Discussion of the positions of the four loops

Moving a Mo range is equivalent to adding the elements of [1,r][1,r] and deleting those of [1,l−1][1,l-1]. Therefore,

  • For l≤rl\le r, the elements of [1,l−1][1,l-1] are effectively added once and then deleted once, the elements of [l,r][l,r] are added once, and the elements of [r+1,+∞)[r+1,+\infty) are never added. This is a valid range.
  • For l=r+1l=r+1, the elements of [1,r][1,r] are effectively added once and then deleted once, while the elements of [r+1,+∞)[r+1,+\infty) are never added. This represents the empty range.
  • For l>r+1l>r+1, the elements of [r+1,l−1][r+1,l-1] (a nonempty interval) are deleted once but never added, so the number of times these elements are added is negative.

Therefore, if l>r+1l>r+1 ever occurs, some element has been added a negative number of times. This causes problems in some tasks: for example, if a set stores all values in the range, we may need to delete an element that is not in the set.

The four while loops in the code have 4!=244!=24 possible orders. Assume the first loop operates on the left endpoint; then there are the following 1212 orders (the other 1212 are symmetric). The table lists the correctness of these 12 forms and counterexamples for the incorrect forms.

Loop orderCorrectnessCounterexample or note
l--,l++,r--,r++Incorrectl<r<l′<r′l<r<l'<r'
l--,l++,r++,r--Incorrectl<r<l′<r′l<r<l'<r'
l--,r--,l++,r++Incorrectl<r<l′<r′l<r<l'<r'
l--,r--,r++,l++CorrectThe proof is tedious
l--,r++,l++,r--Correct
l--,r++,r--,l++Correct
l++,l--,r--,r++Incorrectl<r<l′<r′l<r<l'<r'
l++,l--,r++,r--Incorrectl<r<l′<r′l<r<l'<r'
l++,r++,l--,r--Incorrectl<r<l′<r′l<r<l'<r'
l++,r++,r--,l--Incorrectl<r<l′<r′l<r<l'<r'
l++,r--,l--,r++Incorrectl<r<l′<r′l<r<l'<r'
l++,r--,r++,l--Incorrectl<r<l′<r′l<r<l'<r'

Of all 24 orders, only 6 are correct; 2 of their proofs are tedious, so only 4 proofs are given here.

The four correct forms share the following feature: the first two steps expand the range (l-- or r++), and the last two steps shrink the range (l++ or r--). Written this way, the first two steps expand the range and maintain l≤r+1l\le r+1; after the first two steps, l≤l′≤r′≤rl\le l'\le r'\le r necessarily holds, and the last two steps only shrink the range to [l′,r′][l',r'], which still satisfies l≤r+1l\le r+1. Therefore, this ordering is correct.

Implementation

reference code
#include <algorithm>
#include <cmath>
#include <iostream>
using namespace std;
constexpr int N = 50005;
int n, m, maxn;
int c[N];
long long sum;
int cnt[N];
long long ans1[N], ans2[N];

struct query {
  int l, r, id;

  bool operator<(const query &x) const {  // Overload the < operator
    if (l / maxn != x.l / maxn) return l < x.l;
    return (l / maxn) & 1 ? r < x.r : r > x.r;
  }
} a[N];

void add(int i) {
  sum += cnt[i];
  cnt[i]++;
}

void del(int i) {
  cnt[i]--;
  sum -= cnt[i];
}

long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; }

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> n >> m;
  maxn = sqrt(n);
  for (int i = 1; i <= n; i++) cin >> c[i];
  for (int i = 0; i < m; i++) cin >> a[i].l >> a[i].r, a[i].id = i;
  sort(a, a + m);
  for (int i = 0, l = 1, r = 0; i < m; i++) {  // Concrete implementation
    if (a[i].l == a[i].r) {
      ans1[a[i].id] = 0, ans2[a[i].id] = 1;
      continue;
    }
    while (l > a[i].l) add(c[--l]);
    while (r < a[i].r) add(c[++r]);
    while (l < a[i].l) del(c[l++]);
    while (r > a[i].r) del(c[r--]);
    ans1[a[i].id] = sum;
    ans2[a[i].id] = (long long)(r - l + 1) * (r - l) / 2;
  }
  for (int i = 0; i < m; i++) {
    if (ans1[i] != 0) {
      long long g = gcd(ans1[i], ans2[i]);
      ans1[i] /= g, ans2[i] /= g;
    } else
      ans2[i] = 1;
    cout << ans1[i] << '/' << ans2[i] << '\n';
  }
  return 0;
}

Optimizing ordinary Mo's algorithm

Process

Consider the following data:

// Suppose the block size is 2
1 1
2 100
3 1
4 100

Manual simulation shows that the r pointer moves about 300 times. After processing the first block, l=2,r=100l = 2, r = 100; at this point, moving the l pointer twice would give the fourth query's answer. However, we move the r pointer to 1 to obtain the third query's answer, then move it to 100 to obtain the fourth query's answer, adding more than 90 pointer moves. How can this be optimized? We use odd-even sorting.

What is odd-even sorting? For queries in odd-numbered blocks, sort r in ascending order; for queries in even-numbered blocks, sort r in descending order. After processing an odd block, the r pointer handles the even block while returning, then moves toward n to process the next odd block, reducing its movement. In general, this optimization makes the program about 30% faster.

Implementation

Sorting code:

Compressed form

// clang-format off
// A detail will be discussed shortly
int unit; // Block size
struct node {
  int l, r, id;
  bool operator < (const node &x) const {
    return l / unit == x.l / unit ? (r == x.r ? 0 : ((l / unit) & 1) ^ (r < x.r)) : l < x.l;
  }
};

Uncompressed form

struct node {
  int l, r, id;

  bool operator<(const node &x) const {
    if (l / unit != x.l / unit) return l < x.l;
    // Do not write less-than-or-equal (greater-than-or-equal) on the next two lines, or an error will occur (see the detail below)
    if ((l / unit) & 1) return r < x.r;
    return r > x.r;
  }
};
A detail

When using sort to compare two structures, the conditions a<ba < b and b<ab < a must not both be true; otherwise, the program will fail. See common mistakes.

In the compressed form, without the special case r == x.r, the problem above occurs when l is in the same odd block and r is equal (verify it manually). In the uncompressed form, writing less-than-or-equal (greater-than-or-equal) causes the same problem.

References

This content comes from OI Wiki: Mo's algorithm. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.