248小X的区间或值和

题解

题目描述

题目要求计算数组中所有可能的连续子数组的权值和,权值定义为子数组中所有选取两个元素按位或(OR)的值之和。最终结果需要对 (10^9 + 7) 取模。

思路分析

首先我们不妨考虑一个暴力一些的做法,计算所有数对的按位或值之和。
对于数对a[i]a[j],$(0 <= i < j < n)$。想要连续子数组包含 a[i]a[j],那么连续子数组的左端点$l$的范围为$0 <= l <= i$,连续子数组的右端点$r$的范围为$j <= r < n$。因此数对的贡献和为(a[i] | a[j]) * (i + 1) * (n - j)所以我们只需要枚举所有数对,将贡献累加即可

这种方法的时间复杂度为 $O(n ^ 2)$,对于数组长度大于1e5的情况会超时。

参考代码

1
2
3
4
5
6
7
8
mod = int(1e9 + 7)
def solution(n: int, a: list) -> int:
res = 0
for i in range(n):
for j in range(i + 1, n):
res += (a[i] | a[j]) * (i + 1) * (n - j)
res %= mod
return res % mod

这种方法的时间复杂度为 $O(n ^ 2)$,对于数组长度大于1e5的情况会超时。

优化

  • 因为每一个比特位之间相互独立,我们可以分开考虑每一位的贡献。
    对于数对a[i]a[j]的第k个比特位,我们同样可以采用上述的暴力思路去枚举每个比特位之间的贡献。
  • 当前比特位的大小是1 << k,同时注意到当我们固定a[i]而去枚举a[j]时,连续子数组左端点$l$的范围始终是$0<=l<=i$。那我们直接累加所有右端点的取值不就可以了!!!
  • 在实现时,我们枚举连续子数组的右端点而累加左端点的值, 根据按位与的性质,当前比特位为1时,左端点可以取任意的值,而当前比特位为0时,只能和左端点包含1的连续子数组产生贡献。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mod = int(1e9 + 7)

def solution(n: int, a: list) -> int:
res = 0
for i in range(max(a).bit_length()):
cnt0 = cnt1 = 0
for j in range(n):
if a[j] >> i & 1:
res += (cnt1 + cnt0) * (n - j) * (1 << i) % mod
cnt1 += j + 1
else:
res += cnt1 * (n - j) * (1 << i) % mod
cnt0 += j + 1
return res % mod

复杂度分析

  • 时间复杂度:$O(nlogm)$,其中 (n) 是数组长度,(m) 是数组中最大值。
  • 空间复杂度:$O(1)$,使用常数级额外空间。

248小X的区间或值和
https://kongshuilinhua.github.io/2024/12/29/248小X的区间或值和/
作者
FireFLy
发布于
2024年12月29日
许可协议