LC 100259. 划分数组得到最小的值之和
https://leetcode.cn/problems/minimum-sum-of-values-by-dividing-array/description/
这题目题解比较简单,在DP里面的话把 `andValue` 一起加入到了状态里面去了。之前没有仔细想过这个状态数量问题,这个我觉得题解解释比较清楚:因为And操作每次都会去消耗bit或者是保持bit, 所以其实 `andValue` 这个空间接近常量 \(log(max(nums))\)
class Solution:
def minimumValueSum(self, nums: List[int], andValues: List[int]) -> int:
n, m = len(nums), len(andValues)
INF = 1 << 30
import functools
@functools.cache
def dfs(i, j, andv):
if n - i < m - j: return INF
if j == m:
return 0 if i == n else INF
andv &= nums[i]
if andv < andValues[j]:
return INF
r = dfs(i + 1, j, andv)
if andv == andValues[j]:
r2 = dfs(i + 1, j + 1, -1) + nums[i]
r = min(r, r2)
return r
ans = dfs(0, 0, -1)
return ans if ans != INF else -1
我最开始的思路比较复杂,运行时间很长,但是可以说下。我没有把这个 `andValue` 纳入到dp里面去,而只是记录 `dp[i][j]` 表示 `处理到i元素的时候,划分到了j段`. 动态规划方程可以是这样的。
for i in range(n):
for j in range(m):
if dp[i][j] == INF or (i + 1) == n: continue
if (andValues[j] & nums[i + 1]) == andValues[j]:
# dp[i+1][j] <- dp[i][j] - nums[i] + nums[i+1]
dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] - nums[i] + nums[i + 1])
if (j + 1) < m:
# or we can create a neww group.
p = find_first(i + 1, j + 1)
if p != -1:
dp[p][j + 1] = min(dp[p][j + 1], dp[i][j] + nums[p])
但是这其中有个函数 `find_first` 比较耗时,它做的事情就是寻找min(p), 确保 `Ands(nums[i:p+1]) = andValues[j]`. 里面使用了二分算法比较耗时。
class Solution:
def minimumValueSum(self, nums: List[int], andValues: List[int]) -> int:
n, m = len(nums), len(andValues)
BITS = 20
acc = [[0] * BITS for _ in range(n + 1)]
for i in range(n):
x = nums[i]
for b in range(BITS):
if x & (1 << b):
acc[i + 1][b] += 1
for b in range(BITS):
acc[i + 1][b] += acc[i][b]
def query(s, e):
v = 0
for b in range(BITS):
c = acc[e + 1][b] - acc[s][b]
if c == (e - s + 1):
v = v | (1 << b)
return v
import functools
@functools.cache
def find_first(start, index):
value = andValues[index]
s, e = start, n - 1
while s <= e:
m = (s + e) // 2
v = query(start, m)
if (v | value) == value:
e = m - 1
else:
s = m + 1
if s == n: return -1
v = query(start, s)
if v != value: return -1
return s
INF = 1 << 30
dp = [[INF] * m for _ in range(n)]
p = find_first(0, 0)
if p == -1: return -1
dp[p][0] = nums[p]
for i in range(n):
for j in range(m):
if dp[i][j] == INF or (i + 1) == n: continue
if (andValues[j] & nums[i + 1]) == andValues[j]:
# dp[i+1][j] <- dp[i][j] - nums[i] + nums[i+1]
dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] - nums[i] + nums[i + 1])
if (j + 1) < m:
# or we can create a neww group.
p = find_first(i + 1, j + 1)
if p != -1:
dp[p][j + 1] = min(dp[p][j + 1], dp[i][j] + nums[p])
ans = dp[n - 1][m - 1]
return ans if ans != INF else -1