单调队列优化dp+位运算,LeetCode 3117. 划分数组得到最小的值之和

目录

一、题目

1、题目描述

2、接口描述

cpp

3、原题链接

二、解题报告

1、思路分析

2、复杂度

3、代码详解

cpp


一、题目

1、题目描述

给你两个数组 numsandValues,长度分别为 nm

数组的 等于该数组的 最后一个元素。

你需要将 nums 划分为 m不相交的连续

子数组
,对于第 ith 个子数组 [li, ri],子数组元素的按位 AND 运算结果等于 andValues[i],换句话说,对所有的 1 <= i <= mnums[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、原题链接

3117. 划分数组得到最小的值之和


二、解题报告

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;
    }
};
相关推荐
杰九2 分钟前
【算法题】46. 全排列-力扣(LeetCode)
算法·leetcode·深度优先·剪枝
manba_10 分钟前
leetcode-560. 和为 K 的子数组
数据结构·算法·leetcode
liuyang-neu10 分钟前
力扣 11.盛最多水的容器
算法·leetcode·职场和发展
忍界英雄19 分钟前
LeetCode:2398. 预算内的最多机器人数目 双指针+单调队列,时间复杂度O(n)
算法·leetcode·机器人
Kenneth風车20 分钟前
【机器学习(五)】分类和回归任务-AdaBoost算法-Sentosa_DSML社区版
人工智能·算法·低代码·机器学习·数据分析
C7211BA38 分钟前
使用knn算法对iris数据集进行分类
算法·分类·数据挖掘
Tisfy40 分钟前
LeetCode 2398.预算内的最多机器人数目:滑动窗口+单调队列——思路清晰的一篇题解
算法·leetcode·机器人·题解·滑动窗口
.普通人42 分钟前
c语言--力扣简单题目(回文链表)讲解
c语言·leetcode·链表
程序猿练习生44 分钟前
C++速通LeetCode简单第18题-杨辉三角(全网唯一递归法)
c++·算法·leetcode
Huazzi.1 小时前
算法题解:斐波那契数列(C语言)
c语言·开发语言·算法