📌  相关文章
📜  Q查询更新后所有可能的非空子数组的按位与的按位或(1)

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

查询更新后所有可能的非空子数组的按位与的按位或

给定一个长度为N的数字数组arr,查询更新后所有可能的非空子数组的按位与的按位或。即,对于每个子数组,先对其所有元素求按位与,然后对所有子数组的按位与结果求按位或。

示例
Input: arr = [1, 2, 3]
       queries = [(1, 3, 3), (2, 2, 2), (2, 3, 1)]
Output: [3, 2, 3]
Explanation: 
第一个查询中,只有一个子数组[1, 2, 3],其按位与结果为3。
第二个查询中,只有一个子数组[2],其按位与结果为2。
第三个查询中,子数组为[2]和[2, 3],它们的按位与结果分别为2和2,按位或后结果为3。
解法

我们可以通过枚举所有可能的子数组来计算每个查询的答案,这需要$O(N^3)$的时间复杂度,无法通过本题。因此,我们需要考虑优化算法。

回忆一下按位与和按位或的运算规则:

对于按位与,若当前位的两个运算数都为1,则结果为1;否则结果为0。

对于按位或,若当前位的两个运算数中至少有一个为1,则结果为1;否则结果为0。

假设我们现在已经得到了所有长度为k的子数组的按位与结果,那么如何计算长度为k+1的子数组的按位与结果呢?我们可以考虑将长度为k+1的子数组拆分成两个长度为k的子数组,分别求它们的按位与结果,然后再按位与起来,就得到了长度为k+1的子数组的按位与结果。

但是我们显然不能枚举所有长度为k的子数组来计算按位与结果。相反,我们可以考虑每次将所有元素左移一位,此时按位与的结果将乘以2,同理按位或的结果也将乘以2,然后将当前元素加入到新的子数组中,重复以上步骤即可。

这种做法的时间复杂度为$O(N \cdot \log_2 W)$,其中 $N$ 是数组的长度,$W$ 是元素所占的二进制位数。如果 $W$ 较小,可以通过本题。

代码
from typing import List

def query(arr: List[int], queries: List[Tuple[int, int, int]]) -> List[int]:
    n = len(arr)
    base = 1 << 30
    data = [[0] * n for _ in range(32)]
    for i in range(n):
        x = arr[i]
        for j in range(31):
            if x & base:
                data[j][i] = 1
            x <<= 1

    for i in range(31):
        for j in range(1, n):
            data[i][j] += data[i][j-1]

    ans = []
    for l, r, k in queries:
        k <<= 1
        res = 0
        for i in range(31):
            cnt = data[i][r-1]
            if l > 1:
                cnt -= data[i][l-2]
            if cnt == r-l+1:
                res |= 1 << i
            else:
                res += min(cnt, r-l+1-cnt) * (1 << i)
        ans.append(res)

    return ans

这段代码使用了前缀和的技巧,可以快速计算出所有长度为$k$的子数组的按位与结果。由于按位与和按位或是分别计算的,因此我们需要将每个元素左移一位而不是将整个数组左移。在更新子数组的时候我们需要先将$k$左移一位,然后再将新的元素插入到最低位。

参考文献

https://www.acwing.com/problem/content/description/2575/