目录
一、题目
1、题目描述
给你两个数组
nums
和andValues
,长度分别为n
和m
。数组的 值 等于该数组的 最后一个元素。
你需要将
nums
划分为m
个 不相交的连续子数组
,对于第ith
个子数组[li, ri]
,子数组元素的按位AND
运算结果等于andValues[i]
,换句话说,对所有的1 <= i <= m
,nums[li] & nums[li + 1] & ... & nums[ri] == andValues[i]
,其中&
表示按位AND
运算符。返回将
nums
划分为m
个子数组所能得到的可能的 最小 子数组 值 之和。如果无法完成这样的划分,则返回-1
。
2、接口描述
cpp
cpp
class Solution {
public:
int minimumValueSum(vector<int>& nums, vector<int>& andValues) {
}
};
3、原题链接
二、解题报告
1、思路分析
很明显的dp,我们不难想到定义状态f(j, i) 为 前 i 个数 划分为 j 段的最大收益
但是如何进行状态转移,换言之,固定i,如何找到k所在区间使得and(l, r) = andValues[j]
我们可以二分找左端点,也可以利用 固定一个端点的 前缀位或和 最多有log种取值的性质来获取这个区间
假如区间为[l, r],那么我们f[j, i] = max(f[j - 1, k] + nums[i])
直接枚举是会超时的
由于固定andValues[j],随着 i 的右移,l 也会移动,这就变成了一个不断右移的滑窗,我们可以单调队列维护最值,就把枚举区间内每一个位置变成了均摊O(1)
然后就可以做了
2、复杂度
时间复杂度: O(N M logU)空间复杂度:O(N)
3、代码详解
cpp
cpp
class Solution {
public:
int minimumValueSum(vector<int>& nums, vector<int>& andValues) {
const int inf = 1'000'000'007;
int n = nums.size();
std::vector<int> f(n + 1, inf), nf(n + 1);
f[0] = 0;
for (int target : andValues) {
std::vector<std::pair<int, int>> a;
std::deque<int> dq;
int qi = 0;
nf[0] = inf;
for (int i = 0; i < n; ++ i) {
int x = nums[i];
for (auto &[_and, _] : a)
_and &= x;
a.emplace_back(x, i);
int j = 0, last = -1;
for (auto &[and_, _] : a) {
if (and_ >= target && and_ != last) {
a[j ++] = {and_, _};
last = and_;
}
}
a.resize(j);
if (a.size() && a[0].first == target) {
int r = a.size() > 1 ? a[1].second - 1 : i;
for (; qi <= r; ++ qi) {
while (dq.size() && f[qi] <= f[dq.back()])
dq.pop_back();
dq.push_back(qi);
}
while (dq.front() < a[0].second)
dq.pop_front();
nf[i + 1] = f[dq.front()] + x;
}
else {
nf[i + 1] = inf;
}
}
std::swap(f, nf);
}
return f[n] < inf ? f[n] : -1;
}
};