问题描述
牛妹有一个序列 a1,a2,…,an,她可以对序列执行 kk 次奇怪的操作。每次操作的步骤如下:
- 牛妹选择一个位置 pos(1≤pos≤n)。
- 将 apos在二进制表示下最低位的 11 变成 00。
牛妹想知道在执行 k 次操作之后,序列的总和最小能是多少。
测试样例
样例1:
输入:
n = 2 ,k = 1 ,a = [8, 7]
输出:7
样例2:
输入:
n = 2 ,k = 2 ,a = [9, 6]
输出:6
样例3:
输入:
n = 3 ,k = 2 ,a = [3, 5, 7]
输出:10
解题思路
我们希望在最多 k 次操作后,使序列和最小化。由于每次操作只能消除一个最低位的 1,我们可以通过动态规划(DP)记录操作过程中的最优结果。
1. 二进制最低位 1 的处理
通过位运算,我们可以高效找到并移除二进制数的最低位 1。
设 x 是当前数字:
- 使用
-x & x找到最低位的1。 - 使用
x -= (-x & x)移除该位。
例如:
- x=7(
111),最低位的1是1。 - x=6(
110),最低位的1是2。
2. 动态规划转移方程
状态定义:
- dp[i][j]:前 i 个数,使用最多 j 次操作后,可以得到的最大消除值。
转移方程:
- 不操作当前数: dp[i][j]=dp[i−1][j]
- 使用 t 次操作在当前数上: dp[i][j]=max(dp[i][j],dp[i−1][j−t]+sum(l[i][:t]))其中 l[i]是当前数的所有最低位
1组成的列表,sum(l[i][:t])表示操作 t 次后的累计消除值。
最终答案:
- sum(a)−dp[n][k]即原始总和减去最大可消除值。
代码实现
def solution(n: int, k: int, a: list) -> int:
dp = [[0 for i in range(k+1)] for j in range(n+1)]
for i in range(k+1):
dp[0][i]=0
for j in range(n+1):
dp[j][0]=0
sum_a = sum(a)
l = []
for i in range(n):
temp = []
while a[i]>0:
temp.append(-a[i]&a[i])
a[i]-=temp[-1]
l.append(temp)
for i in range(1,n+1):
for j in range(1,k+1):
temp = []
for num in range(min(len(l[i-1]),j)+1):
temp.append(dp[i-1][j-num]+sum(l[i-1][:num]))
dp[i][j] = max(temp)
return sum_a-dp[n][k]
if __name__ == '__main__':
print(solution(14, 16, [8,10,14,5,16,7,5,14,17,11,10,14,16,9]),47)
print(solution(2, 2, [9, 6]) == 6)
print(solution(3, 2, [3, 5, 7]) == 10)
复杂度分析
-
时间复杂度:
- 预处理 l 阶段:每个数最多有 log(max(a))个最低位
1,总时间复杂度为 O(n⋅log(max(a)))。 - 动态规划阶段:遍历 DP 表,每个单元格最多枚举 k次操作,复杂度为 O(n⋅k2)。
- 综合复杂度为 O(n⋅(log(max(a))+k2))。
- 预处理 l 阶段:每个数最多有 log(max(a))个最低位
-
空间复杂度:
- DP 表占用 O(n⋅k) 的空间。
总结
通过位运算和动态规划的结合,我们可以高效解决这类与二进制操作相关的优化问题。本题中的关键是构建合适的状态转移方程,并灵活使用位运算简化预处理步骤。