248小X的区间或值和 从n^2做法到nlogm做法

101 阅读2分钟

题解

题目描述

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

思路分析

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

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

参考代码

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(n2)O(n ^ 2),对于数组长度大于1e5的情况会超时。

优化

  • 因为每一个比特位之间相互独立,我们可以分开考虑每一位的贡献。 对于数对a[i]a[j]的第k个比特位,我们同样可以采用上述的暴力思路去枚举每个比特位之间的贡献。
  • 当前比特位的大小是1 << k,同时注意到当我们固定a[i]而去枚举a[j]时,连续子数组左端点ll的范围始终是0<=l<=i0<=l<=i。那我们直接累加所有右端点的取值不就可以了!!!
  • 在实现时,我们枚举连续子数组的右端点而累加左端点的值, 根据按位与的性质,当前比特位为1时,左端点可以取任意的值,而当前比特位为0时,只能和左端点包含1的连续子数组产生贡献。
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)O(nlogm),其中 (n) 是数组长度,(m) 是数组中最大值。
  • 空间复杂度:O(1)O(1),使用常数级额外空间。