---
# System prepended metadata

title: RMQ

---

<h1>
    Range Minimum Query (RMQ) - Sparse Table
</h1>

<h2>
    1. Range Minimum Query (RMQ)
</h2>

<h3>
Bài toán
</h3>

Cho mảng *A* gồm *N* phần tử và *Q* truy vấn có dạng ($l$,$r$) ($N$, $Q$ $≤$ 10^5^)

. Với mỗi truy vấn, in ra giá trị nhỏ nhất trong mảng $A$ từ $l$ đến $r$
. Ví dụ: $A$ = [3,6,3,9,10,2] $→$ *min*[2…5] = *min*(6,3,9,10) = 10

<h3>
Thuật trâu
</h3>

Ta sẽ duyệt qua tất cả các phần tử có trong đoạn ($l$,$r$) xem giá trị min là bao nhiêu rồi in ra giá trị đó *O(N * Q)* 

=> Do đó với *N, Q* lớn thì thuật toán này sẽ bị TLE

<h3>
Thuật cải tiến
</h3>

Ta nhận thấy để tìm min 1 đoạn ($l$,$r$) ta có thể tìm min đoạn ($l$,$x$) và đoạn ($x + 1$,$r$) rồi so sánh 2 kết quả đó với nhau xong in ra số nhỏ nhất

1. Thay vì duyệt qua từng phần tử, ta có thể duyệt qua từng nhóm 2
 phần tử. Từ đó, ta có thể giảm thời gian truy vấn xuống còn *O(N/2)*
 
 ```
 int a[N], a2[N];
void pre() 
{
    for(int i = 1 ; i < n; ++i)
        a2[i] = min(a[i], a[i + 1]);
}
int xuly(int l, int r) 
{
    int len = r - l + 1;
    
    if (len == 1) return a[l];

    int mn = INT_MAX;
    for(int i = l ; i < r ; i += 2) mn = min(mn, a2[i]);
    
    // khi kết thúc vòng lặp phía trên, có thể còn 1 số phần tử chưa được xét
    // ví dụ [l, r] = [3, 7], ta chỉ mới xét a[3...6] chứ chưa xét a[7]
    // do đó ta chỉ cần xét thêm a2[r - 1] nữa là đủ do a2[r - 1] = min(a[r - 1], a[r])
    // gtri vẫn nằm trong đoạn [l, r] mà đề bài yêu cầu
    
    mn = min(mn, a2[r - 1]);
    
    return mn;
}
```

2. Tiếp tục cải tiến như phần 1: Thay vì duyệt qua từng nhóm 2 phần tử, ta có thể duyệt qua từng nhóm 4 phần tử. Từ đó, ta có thể giảm thời gian truy vấn xuống còn *O(N/4)*

```
int a[N], a2[N], a4[N];
void pre() 
{
    for(int i = 1 ; i < n; ++i)
        a2[i] = min(a[i], a[i + 1]);
        
     for(int i = 1 ; i + 3 <= n; ++i)
        a4[i] = min(a2[i], a2[i + 1]);
}
int xuly(int l, int r) 
{
    int len = r - l + 1;
    
    if (len == 1) return a[l];
    
    else if (len < 4)
    {
        return min(a2[l], a2[r - 1]);
        // do a[l, l + 1] và a[r - 1, r] sẽ nằm trong đoạn [l, r] mà đề bài yêu cầu
    }

    int mn = INT_MAX;
    for(int i = l ; i + 3 <= r ; i += 4) mn = min(mn, a4[i]);
    
    // tương tự như phần 1 với những TH thiếu 1 số số chưa được xét ta chỉ cần xét thêm a4[r - 3] là đủ 
    
    mn = min(mn, a4[r - 3]);
    
    return mn;
}
```

3. Cứ tiếp tục như vậy ta sẽ tối ưu thuật toán đến log~2~*(N)*
 
 - Nếu dùng mảng *a*(log~2~(*N*)) [*N*] thì rất loằng ngoằng => dễ nhầm lẫn.
 - Ta có thể gọi:
     + $st[j][i]$ là giá trị lớn nhất của 2^j^ phần tử tính từ i(min của $a$[i...i+2^j^−1]), tương ứng với $a$(2^j^)[i]
     + Ta có : 
        Nếu $j* = 0$ => $st[j][i] = a[i]$
        Nếu $j* > 0$ => $st[j][i] = min(st[j−1][i],st[j−1][i+2j−1])$
- Khi đó độ phức tạp của bài toán sẽ là:
    + Tiền xử lý: *O*(*N* log *N*)
    + Mỗi truy vẫn *O*(*1*)
    + Có Q truy vấn, vì thế tổng độ phức tạp thời gian là *O*(*N*log*N*+*Q*)
        
```
// __lg(x) là hàm trả về log2 của x
// ví dụ: N = 10^5 thì __lg(N) = 16 vì 2^16 = 65536
// LOG = __lg(N)

int a[N], st[LOG + 1][N];

void pre() 
{
    for(int i = 1 ; i <= n ; ++i) st[0][i] = a[i];
    
    for(int j = 1 ; j <= LOG ; ++j)
        for(int i = 1 ; i + (1 << j) - 1 <= n ; ++i)
            st[j][i] = min(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]);
}

int xuly(int l, int r) 
{
    int x = __lg(r - l + 1);
    
    return min(st[x][l], st[x][r - (1 << x) + 1]);
}
```
   
<h2>
    2. Range Sum Queries (RSQ)
</h2>

<h3>
    Bài toán
</h3>

Cho mảng $A$ gồm $N$ phần tử và $Q$ truy vấn có dạng ($l$,$r$)
. Với mỗi truy vấn, in ra tổng các phần tử trong mảng $A$ từ $l$ đến $r$
. Giới hạn: $N$, $Q$ $≤$ 10^5^

<strong>Nguồn bài : [https://lqdoj.edu.vn/problem/cses1646](https://)</strong>

<strong>Nhận xét:</strong> Ta luôn có thể tách một số nguyên dương thành tổng các lũy thừa phân biệt của 2 (hệ nhị phân). VD: 18 = 2^4^ + 2^1^

Từ nhận xét trên, ta có thể tách $[l…r]$ thành log2 đoạn có độ dài 2^x^ như sau:

- Đặt $len$ = r − l + 1
- Duyệt $j$ từ 0 đến *__lg(len)*, nếu bit thứ $j$ của $len$ là $1$ thì:

    + Ta tách $[l…r]$ thành *[l…l+2^j^−1]* và *[l+2^j^…r]*
    + *l*=*l*+*2^j^* (tiếp tục tách *[l+2^j^…r]* như *[l…r]*)

```
// __lg(x) là hàm trả về log2 của x
// ví dụ: N = 10^5 thì __lg(N) = 16 vì 2^16 = 65536
// LOG = __lg(N)

int a[N], st[LOG + 1][N];

void pre() 
{
    for(int i = 1 ; i <= n ; ++i) st[0][i] = a[i];
    
    for(int j = 1 ; j <= LOG ; ++j)
        for(int i = 1 ; i + (1 << j) - 1 <= n ; ++i)
            st[j][i] = st[j - 1][i] + st[j - 1][i + (1 << (j - 1))];
}

int xuly(int l, int r) 
{
    int len = r - l + 1;
    int s = 0;
    
    for(int j = 0 ; (1 << j) <= len ; ++j)
    {
        if (len >> j & 1) 
        {
            s = s + st[j][l];
            l = l + (1 << j);
        }
    }
    
    return sum;
}
```

<h2>
3. Khi nào chúng ta nên sử dụng RMQ ?
</h2>

- Nên sử dụng RMQ cho những bài toán không có yêu cầu thay đổi các phẩn tử trong mảng do RMQ không thể giải được các bài toán đó.
- Nên sử dụng RMQ khi số lượng truy vấn $1e6$ $≤$ $Q$ $≤$ $1e7$ do độ phức tạp trong mỗi truy vấn của thuật toán này là $O(1)$ tránh bị TLE.

<h2>
4. Luyện tập
</h2>

- [Library Checker - Static RMQ](https://judge.yosupo.jp/problem/staticrmq)
- [VNOJ - AVLBIT (Dãy cấp số cộng)](https://oj.vnoi.info/problem/avlbit)
- [Codeforces - 5C (Longest Regular Bracket Sequence)](https://codeforces.com/contest/5/problem/C)
- [Codeforces - 1454F (Array Partition)](https://codeforces.com/contest/1454/problem/F)
- [Codechef - FRMQ (Chef and Array)](https://www.codechef.com/problems/FRMQ)
- [Codeforces - 475D (CGCDSSQ)](https://codeforces.com/contest/475/problem/D)
- [Codeforces - 487B (Strip)](https://codeforces.com/contest/487/problem/B)

