How does Mo's algorithm answer range queries?.
April 2, 2023
Learning sources: cp-algorithms on sqrt decomposition, Mo’s with updates.
What is the time complexity of Mo’s algorithm?
Sorting all queries takes .
Note: queries sort as <[L], R> — [L] is the block containing L, R is the query’s right index.
- Let block size be .
cur_lchanges by at most between two queries: calls ofadd(cur_l)/remove(cur_l). - Within one block, queries sharing a left block sort by right index, so
cur_rmoves at most per block. Over all blocks: calls ofadd(cur_r)/ `remove(cur_r)N/S$ is the block count.
With , add/remove calls total — complexity , where is the add/remove cost.
Note: minimizing by calculus gives . also works fine.
How to improve the runtime of Mo’s algorithm?
-
In odd blocks sort right indices ascending, in even blocks descending. This kills the reset where the right pointer jumps from the end back to the start at every block boundary.
Comparator:
bool operator<(const Query &other) const {
if (l / block_size != other.l / block_size) return l < other.l;
return ((l / block_size) & 1) ? r < other.r : r > other.r;
}
- Exactly isn’t always the fastest block size — if , or may win. More importantly, don’t compute block size at runtime: make it
const, since division by constants is well optimized by compilers.
How does Mo’s algorithm with updates work?
Reference: my SPOJ solution.
The problem adds point updates to range queries. Naively redoing/undoing updates per query costs .
With updates, sort queries as <[L], [R], updatesTillNow>.
Note: the same comparator optimization applies:
bool operator<(const Query &other) const {
if (l / block_size != other.l / block_size)
return l < other.l;
if (r / block_size != other.r / block_size)
return ((l / block_size) & 1) ? r < other.r : r > other.r;
return (((l / block_size) & 1) ^ ((other.l / block_size) & 1))
? updatesTillNow < other.updatesTillNow
: updatesTillNow > other.updatesTillNow;
}
- Total
cur_lchanges: with queries having in block , block contributes — total . - Total
cur_rchanges: within a block, plus per block transition — total . - Total
updatesTillNowchanges: for fixed left and right blocks it ranges freely over to , so changes = distinct block pairs × , i.e. .
Total: . For small or large the middle term vanishes, leaving . Minimizing by calculus: optimal block size , giving blocks.
PS: some source claims is best.