当前位置:首页 > 技术 > 正文内容

寻找最长非递减子序列的区间

访客 技术 2026年10月29日 1

给定一个长度为 \(n\) 的序列,其中每个元素 \(i\) 由一个区间 \([x_i, y_i]\) 表示。任务是找到一个最长的连续子数组(区间),使得存在一个序列 \(v_1, v_2, \dots, v_k\),其中 \(v_j \in [x_{start+j-1}, y_{start+j-1}]\) 对于子数组中的每个元素,且该序列 \(v\) 是非递减的(即 \(v_1 \le v_2 \le \dots \le v_k\)。

数据范围

  • \(20\%\) 的数据:\(n \le 10\)
  • \(40\%\) 的数据:\(n \le 3000\)
  • \(50\%\) 的数据:\(n \le 10^5\)
  • \(100\%\) 的数据:\(n \le 10^6\)

示例

输入

4
1 3
2 4
1 1
3 4
    

输出

2
    

暴力解法 (TLE)

对于 \(n \le 3000\) 的数据,可以尝试枚举所有可能的连续子数组的起始点,然后检查是否存在一个非递减序列。这种方法的时间复杂度为 \(O(n^3)\) 或 \(O(n^2)\)(如果检查优化),对于 \(n=3000\) 可能会超时。

复杂度分析

枚举子数组的起始点 \(i\) 和结束点 \(j\),对于每个子数组 \([i, j]\),检查是否存在非递减序列。最简单的检查方法是使用动态规划,时间复杂度为 \(O((j-i+1)^2)\)。总复杂度 \(O(n^4)\)。优化检查过程可以使用贪心或更快的 DP,将检查复杂度降至 \(O(j-i+1)\),总复杂度 \(O(n^3)\)。即使是 \(O(n^2)\) 的方法,对于 \(n=10^6\) 也是不可行的。

示例暴力代码

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int n;
    std::cin >> n;
    std::vector<int> lower_bound(n + 1);
    std::vector<int> upper_bound(n + 1);
    for (int i = 1; i <= n; ++i) {
        int x, y;
        std::cin >> x >> y;
        lower_bound[i] = std::min(x, y);
        upper_bound[i] = std::max(x, y);
    }

    int max_len = 0;
    for (int i = 1; i <= n; ++i) {
        int current_val = -1; // Sentinel value, assuming values are non-negative
        for (int j = i; j <= n; ++j) {
            // Check if we can extend the non-decreasing sequence
            if (current_val == -1 || lower_bound[j] >= current_val) {
                // If we can extend, update current_val to the minimum possible value for the next step
                current_val = std::max(current_val == -1 ? lower_bound[j] : current_val, lower_bound[j]);
                if (current_val <= upper_bound[j]) {
                    max_len = std::max(max_len, j - i + 1);
                } else {
                    // Cannot satisfy the condition for this j, break inner loop
                    break;
                }
            } else {
                // Cannot extend the non-decreasing sequence with lower_bound[j]
                break;
            }
        }
    }

    std::cout << max_len << std::endl;
    return 0;
}

优化解法 (单调队列)

这个问题可以通过维护一个单调队列来高效解决。我们遍历序列,对于每个元素 \(i\),维护一个关于区间 \([x_i, y_i]\) 的信息。单调队列将帮助我们快速找到满足条件的区间。

维护一个单调队列 \(q\),其中存储的是元素的索引。队列中的索引 \(k\) 满足 \([x_k, y_k]\) 的某个属性(例如 \(x_k\) 或 \(y_k\))是递增的。

在处理元素 \(i\) 时,我们需要执行以下操作:

  1. 移除无效区间: 如果队列首部的索引 \(q.front()\),其对应的区间 \([x_{q.front()}, y_{q.front()}]\) 的上限 \(y_{q.front()}\) 小于当前元素 \(i\) 的下限 \(x_i\),那么这个区间无法作为非递减序列的一部分,将其从队首移除。更进一步,如果队列首部的索引 \(k\) 对应的区间 \([x_k, y_k]\) 无法满足 \(v_{prev} \le x_k\),即 \(y_{prev} < x_k\),那么这个区间也需要被移除。我们维护一个变量 `invalid_start_idx` 来记录当前子数组中第一个无效元素的索引。
  2. 更新队首: 如果队列首部的索引 \(k\) 对应的区间 \([x_k, y_k]\) 使得 \(x_k\) 小于 `invalid_start_idx` 所指向区间的 \(x\) 值,那么这个区间也需要被移除。
  3. 维护单调性: 弹出队列尾部所有索引 \(k\) 使得 \(x_i \ge x_k\)。这是因为对于后续的元素,使用 \(i\) 替代 \(k\) 会提供一个更优(更大或相等)的下限,从而更容易延续非递减序列。
  4. 加入当前元素: 将当前元素 \(i\) 的索引加入队列尾部。

在每次迭代中,当前最长有效子序列的长度可以通过 \(i - invalid\_start\_idx + 1\) 来计算,并更新全局最大长度。

优化代码实现

#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n;
    std::cin >> n;

    std::vector<int> lower_bound(n + 1);
    std::vector<int> upper_bound(n + 1);
    for (int i = 1; i <= n; ++i) {
        int x, y;
        std::cin >> x >> y;
        lower_bound[i] = std::min(x, y);
        upper_bound[i] = std::max(x, y);
    }

    std::deque<int> dq; // Stores indices
    int invalid_prefix_end_idx = 0; // Tracks the end index of the "invalid prefix" of the current potential subarray
    int max_len = 0;

    for (int i = 1; i <= n; ++i) {
        // Remove elements from the front of the deque whose upper bound is too small
        // to extend the sequence from the current invalid_prefix_end_idx
        while (!dq.empty() && upper_bound[dq.front()] < lower_bound[i]) {
             // This element dq.front() can no longer be part of a valid non-decreasing sequence starting from invalid_prefix_end_idx
            // So, the sequence must start after dq.front()
            invalid_prefix_end_idx = dq.front();
            dq.pop_front();
        }
        
        // Ensure the current element can start a valid sequence after the current invalid_prefix_end_idx
        // If the lower bound of the current element is less than the effective value at invalid_prefix_end_idx + 1,
        // we might need to advance invalid_prefix_end_idx.
        // This part is implicitly handled by the next loop.

        // Remove elements from the front that are now "dominated" or made invalid by invalid_prefix_end_idx
        while (!dq.empty() && dq.front() <= invalid_prefix_end_idx) {
            dq.pop_front();
        }

        // Maintain the increasing property of lower bounds in the deque
        // If the current element's lower bound is greater than or equal to the back element's lower bound,
        // the back element is suboptimal for extending sequences.
        while (!dq.empty() && lower_bound[i] >= lower_bound[dq.back()]) {
            dq.pop_back();
        }
        dq.push_back(i);

        // The longest valid sequence ending at i starts at invalid_prefix_end_idx + 1
        // and has length i - invalid_prefix_end_idx.
        max_len = std::max(max_len, i - invalid_prefix_end_idx);
    }

    std::cout << max_len << std::endl;

    return 0;
}

总结

这是一个经典的单调队列应用问题。通过维护一个存储索引的单调递增(基于区间下限)的双端队列,并配合一个指针追踪无效区间的起始点,可以在线性时间内找到最长的满足条件的连续子数组。

标签

提高+/省选−, 动态规划, DP, 单调队列, 队列, POI (波兰), 2011

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。