最大子序列和(连续的长度不超过 m 的非空子序列)

最大子序列和

后面发现这题其实是单调队列优化dp的模版题

题目链接:
https://loj.ac/p/10176
相关题目:
https://www.luogu.com/article/1r3wg4ki

给你一个长度为 n 的整数序列 \({A_1,A_2,\cdots ,A_n}\),要求从中找出一段连续的长度不超过 m 的非空子序列,使得这个序列的和最大。

输入格式

第一行为两个整数 n,m;

第二行为 n 个用空格分开的整数序列,每个数的绝对值都小于 1000。

输出格式

仅一个整数,表示连续长度不超过 m 的最大非空子序列和。

样例

输入复制

6 4
1 -3 5 1 -2 3

输出复制

7

数据范围与提示

对于 50% 的数据,\(1\le n,m\le 10^4\)

对于 100% 的数据,\(1\le n,m\le 2\times 10^5\)

Response:

这题其实是 最大子段和问题 的一个变种,加了一个限制:子段长度不能超过 \(m\)
我们来逐步分析思路:


🔹 思路分析

  1. 最大子段和的经典解法
    如果没有长度限制,经典做法是 Kadane 算法

    \[dp[i] = \max(A[i], dp[i-1] + A[i]) \]

    其中 \(dp[i]\) = 以 \(i\) 个元素结尾 的连续子段的最大和

  2. 加上长度限制
    现在子段长度要 \(\le m\) ,等价于:

    \[\max_{r} \Big( S[r] - \min_{r-m \le l-1 \le r-1} S[l-1] \Big) \]

    • \(S[r]\) 固定时,允许的左端点范围就是 \([r-m, r-1]\)
    • 所以我们要在一个“滑动窗口”里找到最小的前缀和。
  3. 数据结构选择
    为了高效维护窗口中的最小前缀和,可以用:

    • 单调队列(deque) 维护前缀和最小值;

🔹 算法步骤

  1. 计算前缀和数组 \(S\) ,其中 \(S[0]=0\)\(S[i]=A_1+\cdots+A_i\)
  2. 用一个单调队列存储候选的 \(S[l-1]\) ,并保证队列中下标在合法范围 \([r-m, r-1]\)
  3. 枚举右端点 \(r\) ,计算:

    \[\text{ans} = \max(\text{ans}, S[r] - \min_{窗口} S[l-1]) \]

  4. 输出答案。

时间复杂度: \(O(n)\)
空间复杂度: \(O(n)\)

🔹 C++ 实现(OI 风格,含详细注释)

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

const int N = 200000 + 5;
int n, m;
int a[N], S[N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];

    // 前缀和
    S[0] = 0;
    for (int i = 1; i <= n; i++) S[i] = S[i-1] + a[i];

    deque<int> dq; // 存放候选下标 (维护前缀和最小值)
    dq.push_back(0);// ***注意*** 初始可以选 l = 0

    int ans = LLONG_MIN;

    for (int r = 1; r <= n; r++) {
        // 确保队首下标在合法范围内 (l-1 >= r-m)
        while (!dq.empty() && dq.front() < r - m) dq.pop_front();

        // 更新答案:S[r] - S[l-1]
        if (!dq.empty())
            ans = max(ans, S[r] - S[dq.front()]);
			
        // ***这里注意,前面先更新答案,是后面才加入S[r], 这样就可以保证前面最小值不包含S[r],保证答案中下标在合法范围  $[r-m, r-1]
        // 维护单调队列 (保证前缀和递增)
        while (!dq.empty() && S[dq.back()] >= S[r]) dq.pop_back();
        dq.push_back(r);
    }

    cout << ans << "\n";
    return 0;
}
posted @ 2025-08-28 17:28  katago  阅读(19)  评论(0)    收藏  举报