📜  范围最小查询(平方根分解和稀疏表)(1)

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

范围最小查询(平方根分解和稀疏表)

在解决一些范围查询问题时,我们通常需要寻找一个数据结构来维护信息,然后在查询时使用该数据结构来获得所需的结果。这里介绍两种经典的数据结构:平方根分解和稀疏表。

平方根分解

平方根分解是一种在处理范围查询问题时非常有用的技术。具体来说,对于 $n$ 个元素的静态序列 $a$,我们可以将其分成 $\sqrt{n}$ 个块,每个块的大小为 $\sqrt{n}$。我们维护两个数组 $blo$ 和 $bhi$,其中 $blo_i$ 表示第 $i$ 个块的最小元素下标,$bhi_i$ 表示第 $i$ 个块的最大元素下标。然后我们对每个块分别预处理一些信息,例如该块的最大值和最小值等等。

在查询时,我们只需要找到所有包含在查询区间内的整块,并暴力地查询其中元素的最值。然后我们将查询区间的左端点和右端点分别缩减到整块边界上,合并剩下的部分。最后再暴力地查询即可。

平方根分解的时间复杂度为 $O(\sqrt{n})$,空间复杂度为 $O(n\sqrt{n})$。这种方法适用于具有一定规律性的数据结构,例如静态序列中的极值查询。

稀疏表

稀疏表是一种更通用的数据结构,可以处理较为复杂的查询,例如区间和、区间最小值等等。稀疏表的核心思想是利用数组的下标结构,实现一种类似于“2的幂次方”这样的层次化结构,使得我们可以在 $O(1)$ 的时间内查询区间的最值或和。

具体来说,我们先将原序列拆成 $log_2{n}$ 层。第 $0$ 层就是原序列,第 $i$ 层的每个元素都为其底下一层相邻两个子区间的最值。这样,我们可以在 $O(n\log_2{n})$ 的时间内预处理出整个稀疏表。

在查询时,我们首先通过 $O(1)$ 的操作找到区间中最大的 $2$ 的幂次方长度(即 $k=log_2(len)$)。然后,我们查询区间左右两端各向后跨距 $2^k$ 的 $min$,并取其中的最小值。这样,我们就实现了 $O(1)$ 的查询操作。

稀疏表的时间复杂度为 $O(n\log_2{n})$,空间复杂度也为 $O(n\log_2{n})$。这种方法适用于各种范围查询问题,例如求区间最值、区间和等等。

以下是稀疏表查询的示例代码:

const int MAXN = 1e5 + 5;
int a[MAXN], f[MAXN][20], Log[MAXN];

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

int query(int l, int r) {
    int k = Log[r - l + 1];
    return min(f[l][k], f[r - (1 << k) + 1][k]);
}

int main() {
    int n, q;
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    Log[1] = 0;
    for (int i = 2; i <= n; ++i) Log[i] = Log[i / 2] + 1;
    init_st(n);
    while (q--) {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", query(l, r));
    }
    return 0;
}

以上是关于范围最小查询的介绍,希望能对读者有所启发。