【每日一题】补档 CF487B. Strip | 数据结构杂烩 -> 单调队列 | 困难

这篇具有很好参考价值的文章主要介绍了【每日一题】补档 CF487B. Strip | 数据结构杂烩 -> 单调队列 | 困难。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

题目内容

原题链接

给定一个长度为 n n n 的数组,将这个数组进行拆分成若干个连续子数组,
使得每个子数组的最大值减去最小值小于等于 s s s
且每个子数组的长度大于等于 l e n len len

问最少可以拆分成多少个连续子数组,如果不可以,则输出 − 1 -1 1

数据范围

  • 1 ≤ n , l e n ≤ 1 0 5 1\leq n,len\leq 10^5 1n,len105
  • 0 ≤ s ≤ 1 0 9 0\leq s\leq 10^9 0s109
  • − 1 0 9 ≤ a i ≤ 1 0 9 -10^9\leq a_i\leq 10^9 109ai109

题解

状态定义
d p [ i ] dp[i] dp[i] 表示将前 i i i 个数可以拆分出的最少的连续子数组。

状态转移
d p [ i ] = min ⁡ { d p [ j ] } + 1 dp[i]= \min\{dp[j]\}+1 dp[i]=min{dp[j]}+1

这里需要满足如下两个条件:
1. max ⁡ { a [ j + 1 , ⋯   , i ] } − min ⁡ { a [ j + 1 , ⋯   , i ] } ≤ s 1. \max\{a[j+1,\cdots,i]\}-\min\{a[j+1,\cdots,i]\}\leq s 1.max{a[j+1,,i]}min{a[j+1,,i]}s
2. i − j + 1 ≥ l e n 2. i-j+1\geq len 2.ij+1len

暴力做法

直接枚举所有的 j j j

时间复杂度: O ( n 2 ) O(n^2) O(n2)

优化做法1

考虑如何加速找到所有合法的 j j j
j j j 越小,即 [ j + 1 , i ] [j+1,i] [j+1,i] 这个区间的最大值越大,最小值越小,那么就极值之差就越有可能大于等于 s s s

那么这部分就是满足二段性的,如此就可以二分。

右端点为 i i i ,二分左端点 j j j ,那么 [ j , i ] [j, i] [j,i] 的区间极值之差如果大于 s s s ,那么左端点应该更大,否则应该继续二分尝试减小左端点。

如此二分的时候应该快速找到区间极值,这部分用 R M Q RMQ RMQ 来解决。

我们最终二分出的左端点为 j j j ,那么需要找到区间 [ j − 1 , i − l e n ] [j-1, i-len] [j1,ilen] 中的 d p dp dp 最小值。这部分因为是动态区间求最值,线段树或者优先队列懒 pop 来解决。

时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)

优化做法2

考虑到这里很多都是求区间的极值,而且对于每个右端点,其左端点一定是单调不减的,所以可以考虑双指针。

枚举右端点 r r r,然后移动左端点 l l l,使得区间最大值减去最小值小于等于 s s s

q m a x qmax qmax 是一个单调递减的队列,队头存储的是区间最大值
q m i n qmin qmin 是一个单调递增的队列,队头存储的是区间最小值

如此就可以 O ( 1 ) O(1) O(1) 快速查出区间极值。

此外,我们还需要知道最终得到左端点 l l l ,区间 [ l − 1 , r − l e n ] [l-1,r-len] [l1,rlen] d p dp dp 最小值。这部分同样可以用一个单调递增的队列来维护。

时间复杂度: O ( n ) O(n) O(n)文章来源地址https://www.toymoban.com/news/detail-738434.html

优化做法1代码一

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
const int INF = 0x3f3f3f3f;
const int BIT = 17;

int qmax[BIT][N];
int qmin[BIT][N];
int lg[N];
int a[N];
int n, s, len;
int dp[N];

void init_rmq() {
    for (int i = 2; i <= n; ++i) lg[i] = lg[i >> 1] + 1;
    for (int j = 1; j <= n; ++j) qmax[0][j] = qmin[0][j] = a[j];
    for (int k = 1; k < BIT; ++k)
        for (int j = 1; j + (1 << k) - 1 <= n; ++j) {
            qmax[k][j] = max(qmax[k - 1][j], qmax[k - 1][j + (1 << (k - 1))]);
            qmin[k][j] = min(qmin[k - 1][j], qmin[k - 1][j + (1 << (k - 1))]);
        }
}

int query_seg(int left, int right) {
    int bit = lg[right - left + 1];
    return max(qmax[bit][left], qmax[bit][right - (1 << bit) + 1]) - min(qmin[bit][left], qmin[bit][right - (1 << bit) + 1]);
};

struct Node {
    int l, r;
    int val;
}tr[N << 2];

void build(int u, int l, int r) {
    tr[u] = {l, r, INF};
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
}

int query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) {
        return tr[u].val;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    int ans = INF;
    if (l <= mid) ans = min(ans, query(u << 1, l, r));
    if (r > mid) ans = min(ans, query(u << 1 | 1, l, r));
    return ans;
}

void modify(int u, int p, int x) {
    if (tr[u].l == tr[u].r) {
        tr[u].val = x;
        return;
    }

    int mid = (tr[u].l + tr[u].r) >> 1;
    if (p <= mid) modify(u << 1, p, x);
    else modify(u << 1 | 1, p, x);
    tr[u].val = min(tr[u << 1].val, tr[u << 1 | 1].val);
}

int main()
{
    scanf("%d%d%d", &n, &s, &len);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    init_rmq();
    build(1, 0, n);
    // 考虑每个点 i 向左的最大值和最小值
    // 二分最短的,然后我需要知道这个区间里的最大值减最小值

    // dp[i] 表示前 i 个点需要拆分成的最少段
    for (int i = 1; i <= n; ++i) dp[i] = INF;
    dp[0] = 0;
    modify(1, 0, 0);

    for (int i = len; i <= n; ++i) {
        if (query_seg(i - len + 1, i) > s) continue;

        int left = 1, right = i - len + 1;
        while (left < right) {
            int mid = (left + right) >> 1;
            if (query_seg(mid, i) > s) left = mid + 1;
            else right = mid;
        }

        // 查 left - 1 到 i - len 的最小值
        dp[i] = min(dp[i], query(1, left - 1, i - len) + 1);

        // 单点最小值更新
        if (dp[i] < INF) {
            modify(1, i, dp[i]);
        }
    }

    printf("%d\n", dp[n] == INF ? -1 : dp[n]);

    return 0;
}

优化做法1代码二

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;
const int N = 100010;
const int INF = 0x3f3f3f3f;
const int BIT = 17;

int qmax[BIT][N];
int qmin[BIT][N];
int lg[N];
int a[N];
int n, s, len;
int dp[N];

void init_rmq() {
    for (int i = 2; i <= n; ++i) lg[i] = lg[i >> 1] + 1;
    for (int j = 1; j <= n; ++j) qmax[0][j] = qmin[0][j] = a[j];
    for (int k = 1; k < BIT; ++k)
        for (int j = 1; j + (1 << k) - 1 <= n; ++j) {
            qmax[k][j] = max(qmax[k - 1][j], qmax[k - 1][j + (1 << (k - 1))]);
            qmin[k][j] = min(qmin[k - 1][j], qmin[k - 1][j + (1 << (k - 1))]);
        }
}

int query_seg(int left, int right) {
    int bit = lg[right - left + 1];
    return max(qmax[bit][left], qmax[bit][right - (1 << bit) + 1]) - min(qmin[bit][left], qmin[bit][right - (1 << bit) + 1]);
};

int main()
{
    scanf("%d%d%d", &n, &s, &len);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    init_rmq();

    // 考虑每个点 i 向左的最大值和最小值
    // 二分最短的,然后我需要知道这个区间里的最大值减最小值

    // dp[i] 表示前 i 个点需要拆分成的最少段
    for (int i = 1; i <= n; ++i) dp[i] = INF;
    dp[0] = 0;

    priority_queue<PII, vector<PII>, greater<PII>> heap;

    for (int i = len; i <= n; ++i) {
        if (i == 5) {
            int x = 1;
        }
        if (dp[i - len] < INF) {
            heap.emplace(dp[i - len], i - len);
        }
        if (query_seg(i - len + 1, i) > s) continue;

        int left = 1, right = i - len + 1;
        while (left < right) {
            int mid = (left + right) >> 1;
            if (query_seg(mid, i) > s) left = mid + 1;
            else right = mid;
        }

        // 查 left - 1 到 i - len 的最小值
        while (!heap.empty() && heap.top().second < left - 1) {
            heap.pop();
        }

        if (!heap.empty()) {
            dp[i] = heap.top().first + 1;
        }
    }

    printf("%d\n", dp[n] == INF ? -1 : dp[n]);

    return 0;
}

优化做法2代码

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
const int INF = 0x3f3f3f3f;
int n, s, len;
int a[N];
int dp[N];
struct Queue {
    int q[N]{};
    int hh, tt;
    Queue(): hh(0), tt(-1) {}
    void push(int x) { q[++tt] = x; }
    void pop_back() { --tt; }
    void pop_front() { ++hh; }
    bool empty() const { return hh > tt; }
    int front() const { return q[hh]; }
    int back() const { return q[tt]; }
}qmax, qmin, qdp;

int main()
{
    scanf("%d%d%d", &n, &s, &len);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);

    memset(dp, 0x3f, (n + 1) * sizeof(int));
    dp[0] = 0;

    for (int r = 1, l = 1; r <= n; ++r) {
        // 找到这个区间里的最小值
        while (!qmin.empty() && a[qmin.back()] >= a[r]) qmin.pop_back();
        qmin.push(r);

        // 找到这个区间里的最大值
        while (!qmax.empty() && a[qmax.back()] <= a[r]) qmax.pop_back();
        qmax.push(r);

        // 此时区间 [l, r] 里的最小值和最大值都已确定
        // 我们需要使得挪动左端点,直到区间 max - min <= s
        // 挪动左端点就意味着 qmax 和 qmin 需要进行移动,使得 qmax 和 qmin 的值都是在 [l, r] 之间
        while (!qmin.empty() && !qmax.empty() && a[qmax.front()] - a[qmin.front()] > s) {
            l += 1;
            while (!qmin.empty() && qmin.front() < l) qmin.pop_front();
            while (!qmax.empty() && qmax.front() < l) qmax.pop_front();
        }

        if (r >= len && dp[r - len] < INF) {
            while (!qdp.empty() && dp[qdp.back()] >= dp[r - len]) qdp.pop_back();
            qdp.push(r - len);
        }
        while (!qdp.empty() && qdp.front() < l - 1) qdp.pop_front();

        if (r - l + 1 >= len && !qdp.empty()) {
            dp[r] = dp[qdp.front()] + 1;
        }
    }

    printf("%d\n", dp[n] == INF ? -1 : dp[n]);

    return 0;
}

到了这里,关于【每日一题】补档 CF487B. Strip | 数据结构杂烩 -> 单调队列 | 困难的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • CF1178F1 Short Colorful Strip 题解

    题目描述 这是F题的第一个子任务。F1和F2的区别仅在对于m和时间的限制上 有n+1种颜色标号从0到n,我们有一条全部染成颜色0的长为m的纸带。 Alice拿着刷子通过以下的过程来给纸带染色: 我们按照从1到n的顺序进行染色,进行每次染色时,我们选取一个区间[ai,bi],0=aibi=m,并

    2024年02月01日
    浏览(37)
  • CF1178F2 Long Colorful Strip 题解 搜索

    题目描述 这是 F 题的第二个子任务。F1 和 F2 的区别仅在对于 m m m 和时间的限制上 有 n + 1 n+1 n + 1 种颜色标号从 0 0 0 到 n n n ,我们有一条全部染成颜色 0 0 0 的长为 m m m 的纸带。 Alice 拿着刷子通过以下的过程来给纸带染色: 我们按照从 1 1 1 到 n n n 的顺序进行染色,进行每

    2024年02月01日
    浏览(40)
  • 数据结构 每日一练 :选择 + 编程

    目录 选择 编程 A .   a[0][2*1]     B.  a[1][3]   C.  a[4-2][0]  D.  a[0][2+2] 答案:D 解析:题目给的是一个3行4列的数组,而D选项是 a[0][2+2] = a[0][4],相当于取得是第1行第5列的元素,越界了。需要注意的是数组下表是从0开始的。下标从0开始!下标从0开始!下标从0开始! A. 

    2024年02月09日
    浏览(45)
  • 数据结构:力扣OJ题(每日一练)

    目录 题一:环形链表 思路一: 题二:复制带随机指针的链表  思路一: 本人实力有限可能对一些地方解释的不够清晰,可以自己尝试读代码,望海涵! 给定一个链表的头节点   head  ,返回链表开始入环的第一个节点。  如果链表无环,则返回  null 。 如果链表中有某个节

    2024年02月12日
    浏览(45)
  • 数据结构:力扣OJ题(每日一练)

    示例:         初始化: 初始化队列Q1,Q2; 入栈: 先将要入栈的数据放入为空的队列中,都为空时,放入Q1; 出栈: 当要出栈时,将Q1的数据出列n-1个,此时的Q1就是栈要出栈的数据(每次出栈都进行一次第三步将为不为空的队列数据放n-1个到为空队列中)); 获取栈顶元

    2024年02月12日
    浏览(41)
  • 数据结构:选择题+编程题(每日一练)

    目录 选择题: 题一: 题二: 题三: 题四: 题五: 编程题: 题一:单值二叉树 思路一: 题二:二叉树的最大深度 思路一: 本人实力有限可能对一些地方解释和理解的不够清晰,可以自己尝试读代码,或者评论区指出错误,望海涵! 感谢大佬们的一键三连! 感谢大佬们

    2024年02月06日
    浏览(41)
  • 【每日算法 && 数据结构(C++)】—— 03 | 合并两个有序数组(解题思路、流程图、代码片段)

    An inch of time is an inch of gold, but you can’t buy that inch of time with an inch of gold. An inch of time is an inch of gold, but you can\\\'t buy that inch of time with an inch of gold 给你两个有序数组,请将两个数组进行合并,并且合并后的数组也必须有序 这个题目要求将两个有序数组合并成一个有序数组。在数

    2024年02月11日
    浏览(56)
  • 【每日算法 && 数据结构(C++)】—— 02 | 数组的并交集(解题思路、流程图、代码片段)

    When you feel like giving up, remember why you started. 当你想放弃时,请记住为什么你开始 给你两个数组,请分别求出两个数组的交集和并集 在数学中,我们可以通过交集和并集来描述两个集合之间的关系。 交集(Intersection) :指的是两个集合中共有的元素组成的集合。可以用符号

    2024年02月11日
    浏览(53)
  • 【每日算法 && 数据结构(C++)】—— 13 | 求最长自增子序列(解题思路、流程图、代码片段)

    Today’s quote is: \\\"Actions speak louder than words. 今天的一句话是:“行动胜于言辞 求最长递增子序列 最长递增子序列是指在给定序列中,找到一个最长的子序列,使得子序列中的元素按照递增的顺序排列。 例如,对于序列 [1, 3, 2, 5, 4, 7, 6],其中的最长递增子序列可以是 [1, 2, 4,

    2024年02月12日
    浏览(41)
  • Leetcode-每日一题【剑指 Offer 26. 树的子结构】

    输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构, 即 A中有出现和B相同的结构和节点值。 例如: 给定的树 A:      3     /    4   5   /  1   2 给定的树 B:    4    /  1 返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点

    2024年02月13日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包