小E的按位与挑战
问题描述
小E有一个长度为n的数组,她想从中选择一个或多个数,使得这些数的按位与(AND)的结果不为0,并且这个结果可以被2^m整除。她的目标是选取这样的数,使得整数m尽可能地大。
你需要帮助小E找到能够使得m最大化的子集。
题目分析
这道题目的算法输入是一个数组,输出一个m,使得我们选取的所有数按位与的结果能够被2^m整除,且使得整数m尽可能地大。
我认为这道题目的核心在于选择合适的数,使得可以和已经选择过的数发生并运算,使得新形成的数组的与运算得到的结果对应的m更大。也就是说,这道题我们可以使用贪心算法解题。
因此,这道题目我们要实现的算法需要两个
- 选择新的数时满足新选择的数会将已选择的数的最后一个1发生并运算变成0
- 避免新选择的数太小导致最终已选择的数在并操作后位数变小
算法1就是我们的贪心策略,算法2则是为了保证我们最后得到的m尽可能的大,不一定是最大值,但是也足够大。
实现算法的步骤
我们首先对输入的数组a进行排序,方便我们在接下来的操作中选择尽可能大的数值加入我们的选择的数
a.sort(reversed=True)
为了保证在m最大时我们退出我们选择数的这一段代码,而且由于此时我们没有新选择任何数,因此我们可以把没有新选择任何数作为退出循环的依据。
canMIncrease = True
while canMIncrease:
canMIncrease = False
if 讨论是否选择某个数:
选择了某个数
canIncrease = True
m += 1
把这个数加入我们选择的数组
接着,我们选择我们的第一个数,我们要求这个数尽可能的大,同时也满足num & 1 == 0。为了实现这一操作,我们需要给我们的and_ans初始化为一个我们不会在运算中使用到的数,比如None,本题里我们不会使用负数,所以此处我们使用 and_ans = -1 作为初始化。并在之后的遍历中使用and_ans == -1作为一个条件分支讨论我们选择第一个数时的情况。
if ans == -1:
ans = i
else:
ans = ans & i
并运算的一个特点是,满足交换律,并且有自反性,即num & num = num和(num1 & num2) & num3 = num,所以我们不必使用数组存储我们已经选择的数,因为若num已经经过与运算加入了and_sum中,则ans_sum & num = and_sum,即再次进行与运算也不会对结果产生影响,因此此题中,我们抽象地把and_sum作为上文我们讨论的选择的数组成的数组这个角色。
我们决定仅靠改变我们选择的第一个数来遍历所有m最大的情况
m_max = 0
for i in a:
m = 0
if i & 1 == 0:
讨论它作为第一个选择后的数的情况
if m > m_max:
m_max = m
同时我们也要编写一个最合适的遍历算法,使得我们每一个选择的新的数都是当前的最优解
我们判断一个数是否是最优的数的判断依据有2个
- 是否是满足
num & (2**m) == 0的数中最大的num - 是否和ans_sum进行与运算不为0
由于我们之前已经对a进行排序,最先选择的符合条件的数一定是最大的,这个数如果也满足依据2,我们就将他作为我们新选择的数
if i >= 2**m and i & (2**m) == 0 and ((i & ans) != 0 or ans == -1):
if ans == -1:
ans = i
else:
ans = ans & i
canMIncrease = True
break
至此,算法的所有部分都已经讨论完毕。
点击提交,我们也通过了所有的测试用例,验证了算法的正确性。