Translation Notice
This article was machine-translated using DeepSeek-R1.
- Original Version: Authored in Chinese by myself
- Accuracy Advisory: Potential discrepancies may exist between translations
- Precedence: The Chinese text shall prevail in case of ambiguity
- Feedback: Technical suggestions regarding translation quality are welcomed
Preface
It’s been a while since the last update to the algorithm notes column. Just learned a new algorithm, so here’s an update…
This is also the first special topic of this column, covering three data structures. If you spot any issues, please feel free to point them out. Thanks!
About RMQ Problems
RMQ stands for Range Minimum/Maximum Query.
In this article, our algorithms focus on finding maximum values (minimum values follow similar logic). The problem statement is as follows:
Given a sequence $A=(A_1,A_2,\dots,A_N)$ of length $N$.
There are $Q$ queries. The $i$-th query is:
$\to~$ Given $1\le l_i\le r_i\le N$, find the maximum value in interval $[l_i,r_i]$, i.e., $\displaystyle\max_{j=l_i}^{r_i} A_j$.
Below, we’ll start with a brute-force approach and gradually introduce common solutions for RMQ problems.
Standard RMQ problems (all algorithms except brute-force can pass):
- Luogu P1816 Loyalty (classic RMQ)
- Luogu P2880 [USACO07JAN] Balanced Lineup G
- Luogu P2251 Quality Inspection
- Luogu P8818 [CSP-S 2022] Strategy Game (requires more thinking)
Solutions
Brute-Force
We first read the sequence $A$, then process each query by directly iterating through $A_l\dots A_r$.
Time complexity: $\mathcal O(\sum r_i-l_i)=\mathcal O(NQ)$.
|
|
However, when submitting to Luogu P1816…
Clearly, the time complexity is too high. Further optimization is needed.
Sparse Table
Sparse Table (ST) is a data structure for static RMQ problems.
Static means the original sequence remains unchanged. ST tables cannot handle direct modifications.
ST table initialization takes $\mathcal O(N\log N)$, and each query takes $\mathcal O(1)$.
Storage Structure
$$ st[i][j]=\max\{A_i,A_{i+1},\dots,A_{i+2^j-1}\} $$
Here, $st[i][j]$ represents the maximum value of $2^j$ elements starting from $A_i$, using a doubling strategy.
Initialization
$$ st[i][j]=\max\{st[i][j-1],st[i+2^{j-1}][j-1]\} $$
Fill by iterating $j$ first, then $i$. Total initialization time: $\mathcal O(N\log N)$.
Pseudocode:
|
|
C++ implementation:
|
|
Query
For interval $[l,r)$, find two intervals $[l,a)$ and $[b,r)$ such that their union is exactly $[l,r)$. Let $k=\lfloor\log_2(r-l)\rfloor$:
|
|
Full Implementation
Complete code for Luogu P1816. Time complexity: $\mathcal O(Q+N\log N)$.
|
|
Runtime: $128\mathrm{ms}$
Memory: $6.90\mathrm{MB}$
Fenwick Tree
For Fenwick Tree basics, see this article. Here, we adapt it for RMQ.
Original Algorithm
Fenwick Tree supports prefixSum
and update
in $\mathcal O(\log N)$. It works for any associative operation.
Implementing prefixMax
and update
:
|
|
RMQ Fenwick Tree
Define recursive rangeMax
:
|
|
Non-recursive version for efficiency:
|
|
Full Implementation
Solution for Luogu P1816. Time complexity: $\mathcal O(N+Q\log N)$.
|
|
Runtime: $135\mathrm{ms}$
Memory: $1.14\mathrm{MB}$
Segment Tree
Segment Trees handle a wider range of problems with $\mathcal O(N)$ initialization and $\mathcal O(\log N)$ per query.
Storage Structure
Stored as a heap-like array. Each node represents an interval.
|
|
Build
Recursively construct left and right subtrees:
|
|
Query
Recursively check overlapping intervals:
|
|
Full Implementation
Solution for Luogu P1816. Time complexity: $\mathcal O(N+Q\log N)$.
|
|
Runtime: $163\mathrm{ms}$
Memory: $1.78\mathrm{MB}$
Summary
Comparison of algorithms:
Algorithm | Preprocessing Time | Query Time | Space | Passes Constraints? |
---|---|---|---|---|
Brute-Force | $\mathcal O(1)$ | $\mathcal O(N)$ | $\mathcal O(N)$ | ❌ |
Sparse Table | $\mathcal O(N\log N)$ | $\mathcal O(1)$ | $\mathcal O(N\log N)$ | ✔️ |
Fenwick Tree | $\mathcal O(N)$ | $\mathcal O(\log N)$ | $\mathcal O(N)$ | ✔️ |
Segment Tree | $\mathcal O(N)$ | $\mathcal O(\log N)$ | $\mathcal O(N)$ | ✔️ |
Performance on Luogu P1816:
Algorithm | Runtime | Memory | Code Length |
---|---|---|---|
Sparse Table | $128\mathrm{ms}$ | $6.90\mathrm{MB}$ | $625\mathrm B$ |
Fenwick Tree (non-recursive) | $135\mathrm{ms}$ | $1.14\mathrm{MB}$ | $786\mathrm B$ |
Segment Tree | $163\mathrm{ms}$ | $1.78\mathrm{MB}$ | $905\mathrm B$ |
ST Table is fastest but memory-heavy. Fenwick Tree balances speed and space. Segment Tree is versatile but slightly slower.
This concludes the article. Hope you found it helpful!
This is my first 10k-word article in 2023. Happy New Year!