📌  相关文章
📜  使用 MO 算法查询给定范围内偶数和元素的计数(1)

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

使用 MO 算法查询给定范围内偶数和元素的计数

什么是 MO 算法?

MO 算法是一种常用于查询数据结构中某个范围内的问题的算法。

MO 算法的思想是将询问离线,按照左端点所在块的编号对所有询问进行分组,然后在每组内将询问按照右端点排序。接下来,我们逐个取出分组中的询问,并用线段树等数据结构处理出答案。

使用 MO 算法可以将时间复杂度从 $O(qn)$ 降低到 $O(n\sqrt{n}+q\sqrt{n}\log{n})$。

问题描述

现在有一个长度为 $n$ 的序列 $a$,给出 $q$ 个询问,每个询问包含两个整数 $l$ 和 $r$,需要查询区间 $[l,r]$ 内的偶数个数和元素个数。

解决方案

我们考虑使用 MO 算法来解决这道题目。首先,我们需要将所有询问按照左端点所在块的编号进行分组,并在每个分组内对询问按照右端点排序:

from math import sqrt

n = 10
q = 3
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
queries = [(1, 5), (2, 8), (3, 7)]

block_size = int(sqrt(n))

class Query:
    def __init__(self, left, right, index):
        self.left = left
        self.right = right
        self.index = index

queries = [Query(l, r, i) for i, (l, r) in enumerate(queries)]
queries.sort(key=lambda query: (query.left // block_size, query.right))

对于分组内的询问,我们可以使用线段树来计算答案。以下是完整的代码:

class SegmentTree:
    def __init__(self, n):
        self.n = n
        self.data = [0] * (4 * n)
        self.lazy = [0] * (4 * n)

    def _push_up(self, p):
        self.data[p] = self.data[2 * p] + self.data[2 * p + 1]

    def _push_down(self, p, ln, rn):
        if self.lazy[p]:
            self.lazy[2 * p] ^= 1
            self.lazy[2 * p + 1] ^= 1
            self.data[2 * p] = ln - self.data[2 * p]
            self.data[2 * p + 1] = rn - self.data[2 * p + 1]
            self.lazy[p] = 0

    def build(self, p, l, r, a):
        if l == r:
            self.data[p] = a[l] % 2
        else:
            mid = (l + r) // 2
            self.build(2 * p, l, mid, a)
            self.build(2 * p + 1, mid + 1, r, a)
            self._push_up(p)

    def update(self, p, l, r, ql, qr):
        if ql <= l and r <= qr:
            self.lazy[p] ^= 1
            self.data[p] = r - l + 1 - self.data[p]
        else:
            mid = (l + r) // 2
            self._push_down(p, mid - l + 1, r - mid)
            if ql <= mid:
                self.update(2 * p, l, mid, ql, qr)
            if qr > mid:
                self.update(2 * p + 1, mid + 1, r, ql, qr)
            self._push_up(p)

    def query(self, p, l, r, ql, qr):
        if ql <= l and r <= qr:
            return self.data[p]
        else:
            mid = (l + r) // 2
            self._push_down(p, mid - l + 1, r - mid)
            res = 0
            if ql <= mid:
                res += self.query(2 * p, l, mid, ql, qr)
            if qr > mid:
                res += self.query(2 * p + 1, mid + 1, r, ql, qr)
            return res

block_size = int(sqrt(n))

class Query:
    def __init__(self, left, right, index):
        self.left = left
        self.right = right
        self.index = index

def solve():
    segment_tree = SegmentTree(n)

    counts = [0] * q
    counts_evens = [0] * q

    left = right = 0
    for query in queries:
        while left < query.left:
            segment_tree.update(1, 1, n, left, left)
            left += 1
        while left > query.left:
            left -= 1
            segment_tree.update(1, 1, n, left, left)
        while right < query.right:
            right += 1
            segment_tree.update(1, 1, n, right, right)
        while right > query.right:
            segment_tree.update(1, 1, n, right, right)
            right -= 1

        count = segment_tree.query(1, 1, n, query.left, query.right)
        count_even = segment_tree.query(1, 1, n, query.left, query.right // 2)
        counts[query.index] = count
        counts_evens[query.index] = count_even

    return counts, counts_evens

answers = solve()
print(answers)

总结

本文介绍了如何使用 MO 算法查询给定范围内偶数和元素的计数。MO 算法的时间复杂度为 $O(n\sqrt{n}+q\sqrt{n}\log{n})$,较之于 $O(qn)$ 的朴素算法更加高效。