📌  相关文章
📜  使用MO算法查询值在A到B范围内的元素(1)

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

使用 MO 算法查询值在 A 到 B 范围内的元素

在处理算法问题时,查询区间内某些特定属性的元素是一个很常见的需求。如何快速地完成这样的查询并降低时间复杂度成为了一个挑战。MO 算法就是一种解决这类问题的方法之一。

MO 算法简介

MO 算法是一种基于分块的算法,它将询问按照一定的顺序进行排序,并按照固定大小的块分组。通过预处理块内和块间的相对查询结果,可以使得对于每个询问,其所需的时间复杂度保持在块的大小和询问总数的乘积以内。

MO 算法常用于查询区间内的元素满足某些条件,如区间内不同颜色的数量,出现次数大于 k 的元素,或者出现的奇偶性情况等。

实现思路

我们用一个大小为 $t$ 的数组 $cnt$ 来记录区间内每个元素的出现次数,同时用一个变量 $ans$ 记录当前查询区间内满足条件的元素数量。

对于两个询问 $(l_1,r_1)$ 和 $(l_2,r_2)$,将它们按照所在块的编号和右端点从小到大排序。同时维护两个指针 $L$ 和 $R$,分别表示当前区间的左右端点位置。

在每次处理询问时,考虑如何将当前区间更新为需要的查询区间 $(l,r)$。分为以下几种情况:

  • 当前区间右端点 $R$ 小于 $r$,需要将 $R+1$ 到 $r$ 的元素添加进来;
  • 当前区间右端点 $R$ 大于 $r$,需要将 $r+1$ 到 $R$ 的元素移除;
  • 当前区间左端点 $L$ 小于 $l$,需要将 $L$ 到 $l-1$ 的元素移除;
  • 当前区间左端点 $L$ 大于 $l$,需要将 $l$ 到 $L-1$ 的元素添加进来。

对于第一种情况,我们可以通过将 $[R+1,r]$ 中的元素加入 $cnt$ 数组,更新 $ans$ 的值;对于第二种情况,我们可以通过将 $[r+1,R]$ 中的元素从 $cnt$ 数组中删除,更新 $ans$ 的值;对于第三种情况,我们可以通过将 $[L,l-1]$ 中的元素从 $cnt$ 数组中删除,更新 $ans$ 的值;对于第四种情况,我们可以通过将 $[l,L-1]$ 中的元素加入 $cnt$ 数组,更新 $ans$ 的值。处理完所有询问后,$ans$ 就是答案。

代码实现

下面是查询区间内值在 A 到 B 范围内的元素的示例代码:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;

constexpr int N = 100005, M = 500005;

int n, m, a[N];
int bl_size, cnt[M], ans;
struct Query {
    int l, r, id;
    bool operator<(const Query& q) const {
        return l / bl_size == q.l / bl_size ?
            (r < q.r) ^ ((l / bl_size) & 1) :
            l < q.l;
    }
} Q[M];
vector<int> res;

void add(int x) {
    if (cnt[x] == 0) ++ans;
    ++cnt[x];
}

void del(int x) {
    --cnt[x];
    if (cnt[x] == 0) --ans;
}

void solve() {
    bl_size = sqrt(n);
    for (int i = 0; i < m; ++i) {
        cin >> Q[i].l >> Q[i].r;
        --Q[i].l;
        Q[i].id = i;
    }
    sort(Q, Q + m);

    int L = 1, R = 0;
    for (int i = 0; i < m; ++i) {
        while (L > Q[i].l) add(a[--L]);
        while (R < Q[i].r) add(a[++R]);
        while (L < Q[i].l) del(a[L++]);
        while (R > Q[i].r) del(a[R--]);
        res.push_back(ans);
    }

    for (int i = 0; i < m; ++i) {
        cout << res[i] << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    solve();
    return 0;
}

代码中的 add 函数和 del 函数用于计算添加或删除元素时对答案的贡献。solve 函数中,我们先读入查询的各个区间,将它们按照 MO 算法的规则排序。接着,我们按照 MO 算法的思路,不断更新当前区间的左右端点,直到达到当前询问区间。最后的结果存入了结果数组 $res$ 中,可以输出。

总结

MO 算法是一种快速解决区间询问的算法,尤其适用于需要支持动态添加和删除元素的场景,如区间众数、区间不同数的数量等。它的时间复杂度通常是线性或者线性对数级别,可以帮助我们更快地完成查询任务。