📌  相关文章
📜  范围查询以计算位于给定范围内的元素:MO的算法(1)

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

范围查询以计算位于给定范围内的元素: MO's算法

简介

MO's算法,是一种支持查询操作的离线算法,用于解决具有静态数据结构的区间查询问题,例如区间众数、区间不同数字个数等问题。MO's算法的时间复杂度为 $O(\sqrt{n}q\log n)$,其中 $n$ 为元素个数,$q$ 为查询次数。

MO's算法通过对查询操作进行块状分割,将原本的线性扫描转化为块状扫描,从而达到优化的目的。同时,通过巧妙地设计块的大小,可以使得查询操作的时间复杂度尽量地小,达到较好的效果。

实现

MO's算法的实现有两个关键步骤:块的划分和查询操作。

块的划分可以采取以下的方法:首先,将所有查询按照左端点所在的块进行分类,即将所有左端点位于 $[k\cdot B,(k+1)\cdot B)$ 区间内的查询放在一起;然后,对每个分类后的查询按照右端点进行排序。注意,这里的块大小 $B$ 应该使得每个块包含 $\sqrt{n}$ 个元素。

在排序后,我们可以开始进行查询操作。为了保证查询的正确性,并且尽可能地减少每次查询操作的时间复杂度,我们需要采取以下的措施:

  • 按顺序依次处理每个查询,扫描整个数据结构,计算出对于每个查询的答案。
  • 对于每个查询,我们需要保证其左右端点都尽可能地接近上一个查询操作的左右端点,从而保证每次查询操作只需要添加或删去少量的元素。

例如,我们假设当前的查询为 $(L,R)$,上一个查询为 $(L',R')$。

如果 $L < L'$,我们向左扩大当前查询区间,直到到达 $L'$ 的位置。

如果 $R' < R$,我们向右扩大当前查询区间,直到到达 $R'$ 的位置。

在上述操作完成之后,我们可以用类似于双指针的方法,快速地计算出 $(L,R)$ 区间内的答案。

代码实现

下面是使用C++语言实现的MO's算法程序:

const int N = 1e5 + 5;
const int S = 1e3 + 5;

struct Query {
    int l, r, id;
};

int n, q, a[N], cnt[N], ans[N], res;
Query qry[N];

bool cmp(const Query &a, const Query &b) {
    if (a.l / S != b.l / S) {
        return a.l < b.l;
    }
    return (a.l / S & 1) ? a.r < b.r : a.r > b.r;
}

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

void del(int x) {
    if (cnt[a[x]] == 1) {
        --res;
    }
    --cnt[a[x]];
}

int main() {
    cin >> n >> q;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    for (int i = 1; i <= q; ++i) {
        int l, r;
        cin >> l >> r;
        qry[i] = {l, r, i};
    }
    sort(qry + 1, qry + q + 1, cmp);
    int l = 1, r = 0;
    for (int i = 1; i <= q; ++i) {
        int ql = qry[i].l, qr = qry[i].r, id = qry[i].id;
        while (l < ql) {
            del(l++);
        }
        while (l > ql) {
            add(--l);
        }
        while (r < qr) {
            add(++r);
        }
        while (r > qr) {
            del(r--);
        }
        ans[id] = res;
    }
    for (int i = 1; i <= q; ++i) {
        cout << ans[i] << "\n";
    }
    return 0;
}

其中,我们定义 cmp 函数来进行查询操作的排序。注意到, cmp 函数的实现过程中,我们在每个块中进行函数值的取反,从而保证每个块中的查询排序次序都是按照一定规则排列的。

在查询时,我们在满足上一个查询的左右端点的基础上,向左或向右扩展当前查询区间,然后使用双指针的方法计算出区间内的值。

最后,我们将计算好的答案输出到标准输出中。

总结

MO's算法是解决区间查询问题的一种有效方法。通过块状分割,将原本线性扫描的时间复杂度优化为块状扫描的时间复杂度,同时通过巧妙地设计块的大小,可以使得查询操作的时间复杂度尽量地小。