📌  相关文章
📜  用于最小化添加到数组中给定范围的总和的查询,以使其按位与非零(1)

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

最小化范围内元素的总和

问题描述

给定一个长度为n的数组a,以及若干次查询,每次查询给出l和r,要求在[l,r]范围内选择一些元素,使得它们按位与的结果非零,同时最小化这些元素的总和。

解决方案

我们首先考虑如何找到所有满足条件的元素。一个朴素的做法是枚举所有可能的子集,判断它们的按位与是否非零。这个做法的时间复杂度是指数级别的,显然不可行。

我们可以采用一种基于二进制的做法。注意到题目要求的是按位与非零,因此我们只需要找到一些二进制位,这些位在[l,r]范围内全部为1,同时存在这些位的下标对应的二进制位,元素的按位与值就一定非零。具体地,我们可以从高位到低位依次考虑每一位,设当前考虑的二进制位编号为k(从高到低的二进制位编号)。如果[l,r]范围内所有二进制位的编号大于或等于k的位都是1,那么我们将第k位也设为1,否则设为0。最终得到的这个二进制数(设它为mask)就是我们要求的包含二进制位k的子集(对应的元素就是二进制位k为1的元素)所对应的按位与值非零的最小元素和。为了得到[l,r]范围内的解,我们只需要计算所有在[l,r]范围内的k对mask的贡献(即k对应的二进制位为1,则将mask的第k位设为1),把它们的求和即可。具体地,令leftmost(k)表示[l,r]范围内最左侧的二进制位的编号(从高到低的二进制位编号),我们有以下算法:

def solve(l, r, a):
    mask = 0
    ans = 0
    for k in range(31, -1, -1):
        if mask | (1 << k) > r:
            continue
        if mask | (1 << k) >= l:
            ans += (mask | (1 << k))
            continue
        cnt = 0
        for i in range(l, r+1):
            cnt += (a[i] >> k) & 1
        if cnt > 0:
            mask |= (1 << k)
            ans += (mask | (1 << k))
    return ans

代码中,我们首先从高位到低位依次考虑每个二进制位,设当前二进制位编号为k。如果mask | (1 << k) > r,说明调整mask的第k位后会超出[l,r]的范围,所以我们忽略这种情况,直接跳过本次循环。否则,如果mask | (1 << k) >= l,说明调整mask的第k位后得到的子集包含[l,r]范围内的元素,因此我们直接计算这个子集对应的元素和,然后跳过剩下的二进制位。否则,我们需要计算在[l,r]范围内,第k位为1的元素个数cnt。如果cnt>0,说明在[l,r]范围内至少存在一个元素,它的第k位为1,因此我们将mask的第k位设为1,然后计算包含mask的所有元素,它们的第k位为1的元素所构成的子集对应的元素和,作为贡献加入答案。最终得到的答案即为所有计算过的贡献的总和。

时间复杂度

每次查询都要从高位到低位依次考虑每个二进制位,每个二进制位最多需要扫描[l,r]范围的元素一次,因此总时间复杂度为O(32nq),其中q是查询次数。

参考代码
def solve(l, r, a):
    mask = 0
    ans = 0
    for k in range(31, -1, -1):
        if mask | (1 << k) > r:
            continue
        if mask | (1 << k) >= l:
            ans += (mask | (1 << k))
            continue
        cnt = 0
        for i in range(l, r+1):
            cnt += (a[i] >> k) & 1
        if cnt > 0:
            mask |= (1 << k)
            ans += (mask | (1 << k))
    return ans
参考资料