📜  使用 sqrt 分解查询 rmq - C++ (1)

📅  最后修改于: 2023-12-03 15:06:51.240000             🧑  作者: Mango

使用 sqrt 分解查询 rmq - C++

本文将介绍使用 sqrt 分解进行 RMQ(最小值查询),RMQ 是一种经典问题,在许多数据结构和算法中都有涉及,它的目标是在一段区间内查询最小值。我们通常使用一些数据结构,例如线段树,ST 表等,但在这里,我们将介绍另一种称为 sqrt 分解的方法。

问题描述

给定一个包含 n 个元素的数组 a,以及 m 个询问,每个询问包含两个整数 l 和 r(1 <= l <= r <= n),询问数组 a 在区间 [l, r] 范围内的最小值。

算法思路

sqrt分解算法的思路如下:

  1. 将原数组划分为 $sqrt(n)$ 个块
  2. 对于每个块,预处理出一个最小值和它所代表的位置
  3. 对于询问 [l, r],分别计算 [l, l_block_end],(l_block_end, r_block_start],[r_block_start, r],以及每个完整块的最小值
  4. 取所有值中的最小值

由于每个块中的元素数量为 $sqrt(n)$,那么处理每个块的最小值可以使用 O($sqrt(n)$) 的时间复杂度,在 $m$ 个询问中,每一次询问最差情况下需要处理的区间数量为 $3$,以及每个完整块的数量为 $\frac{n}{sqrt(n)}=\sqrt(n)$,因此总时间复杂度为 O($m\sqrt(n)$)。

代码实现

首先,我们需要先预处理出所有块中的最小值和它所代表的位置。

const int MAXN = 1e5;
const int SQRN = 316; // sqrt(MAXN) 
int n, a[MAXN + 5], b[SQRN + 5], pos[MAXN + 5], S;

void prework() {
    S = sqrt(n);
    for (int i = 1; i <= n; i++) {
        b[pos[i] = (i - 1) / S + 1] = i;
        if (i % S == 1) {
            int f = pos[i];
            for (int j = i; j <= min(n, i + S - 1); j++) {
                if (a[j] < a[b[f]]) b[f] = j;
            }
        }
    }
}

然后,我们需要分别计算出 l、r 所在块中的最小值,并计算所有完整块的最小值,比较它们之中的最小值。

int query(int l, int r) {
    int ans = INT_MAX;
    if (pos[l] == pos[r]) {
        for (int i = l; i <= r; i++) ans = min(ans, a[i]);
        return ans;
    }
    for (int i = l; pos[i] == pos[l]; i++) ans = min(ans, a[i]);
    for (int i = r; pos[i] == pos[r]; i--) ans = min(ans, a[i]);
    for (int i = pos[l] + 1; i < pos[r]; i++) ans = min(ans, a[b[i]]);
    return ans;
}

最后,我们将预处理和查询整合到一个主函数中:

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    prework();
    int m;
    cin >> m;
    while (m--) {
        int l, r;
        cin >> l >> r;
        cout << query(l, r) << endl;
    }
    return 0;
}
总结

这篇文章介绍了如何使用 sqrt 分解方法解决 RMQ 问题,它的时间复杂度为 O($m\sqrt(n)$),要比线段树和 ST 表更加高效。同时,这种方法也可以用来解决其他问题,例如区间众数查询。