📅  最后修改于: 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/