题解:学而思编程 最大子段和3

【题目来源】

学而思编程:最大子段和3

【题目描述】

皮皮刚刚学完最大子段和,老师为了检测皮皮是否真正掌握,决定对它进行提问。

给出数组 \(a\) 的元素 \(a[1],a[2],⋯ ,a[n]\) 的值,给定一个 \(1\) ~ \(n\) 之间的位置 \(pos\),请问 \(a[pos]\)~\(a[n]\) 之间的最大子段和是多少?请回答 \(m\) 个这样的问题。

【输入】

\(1\) 行,是两个正整数 \(n\)\(m\),分别表示数组 \(a\) 的长度和询问的次数。

\(2\) 行,用空格隔开的 \(n\) 个整数,依次是 \(a[1],a[2],⋯ ,a[n]\) 的值。

接下来的 \(m\) 行,每行一个正整数 \(pos\),表示询问的位置。

【输出】

\(m\) 行,每行一个整数,为所求的最大子段和。

【输入样例】

6 1
1 -6 5 -4 2 4
6

【输出样例】

4

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的序列 \(a_1, a_2, \ldots, a_n\),需要回答 \(m\) 个查询:对于每个查询位置 \(pos\),求子数组 \(a[pos..n]\) 的最大子段和。这是一个后缀区间最大子段和查询问题,需要预处理来支持 \(O(1)\) 查询。

  2. 算法选择

    • 后缀DP预处理\(dp[i]\) 表示从位置 \(i\) 开始到 \(n\) 的最大子段和
    • 两次DP:第一次计算以 \(i\) 开头的最大子段和,第二次计算区间 \([i, n]\) 的最大子段和
  3. 关键步骤

    • 第一次DP(从后向前):计算以 \(a[i]\) 开头的最大子段和
      • \(dp[i] = \max(dp[i+1] + a[i], a[i])\)
      • 表示要么从 \(a[i]\) 开始新的一段,要么将 \(a[i]\) 接在 \(dp[i+1]\) 对应的子段前面
    • 第二次DP(从后向前):将 \(dp[i]\) 转换为区间 \([i, n]\) 的最大子段和
      • \(dp[i] = \max(dp[i+1], dp[i])\)
      • \(dp[i+1]\) 表示从 \(i+1\) 开始的最大子段和
      • \(dp[i]\)(第一次DP结果)表示以 \(i\) 开头的最大子段和
      • 取两者最大值即为区间 \([i, n]\) 的最大子段和
    • 回答查询:对于每个 \(pos\),直接输出 \(dp[pos]\),时间 \(O(1)\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + m)\),预处理 \(O(n)\),每次查询 \(O(1)\)
    • 空间复杂度:\(O(n)\),存储 \(dp\) 数组和原数组
  5. 后缀预处理 + 区间查询的核心思想

    • 离线预处理:通过两次DP将区间查询问题转化为 \(O(1)\) 查表
    • 第一次DP:求解"以 \(i\) 开头的最大子段和"
    • 第二次DP:求解"从 \(i\)\(n\) 的某个位置的最大子段和"
    • 区间最值:\(\max(\)\(i\) 开头的子段,从 \(i+1\) 开始的最优子段\()\)
    • 适用于多次查询同一类区间问题的场景,用空间换时间

【算法标签】

线性DP-一维

【代码详解】

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

#define int long long
const int N = 100005;
int n, a[N], dp[N], pos, maxn = -1e9, m;  // n: 数组长度,a: 数组元素,dp: 动态规划数组,pos: 查询位置,m: 查询次数

signed main()
{
    cin >> n >> m;  // 读入数组长度和查询次数
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];  // 读入数组元素
    }

    // 从后向前计算最大子段和
    // dp[i]表示以a[i]开头的最大子段和
    for (int i = n; i >= 1; i--)
    {
        dp[i] = max(dp[i + 1] + a[i], a[i]);  // 要么从当前位置开始,要么加上后面的
    }

    // 从后向前处理,dp[i]表示从i到n的最大子段和
    for (int i = n - 1; i >= 1; i--)
    {
        dp[i] = max(dp[i + 1], dp[i]);  // 更新为从i到n的最大子段和
    }

    // 调试输出
    // for (int i=1;i<=n;i++)
    // 	cout<<dp[i]<<' ';
    // cout<<endl;

    // 处理查询
    while (m--)
    {
        cin >> pos;  // 读入查询位置
        cout << dp[pos] << endl;  // 输出从pos位置开始到结尾的最大子段和
    }
    return 0;
}

【运行结果】

6 1
1 -6 5 -4 2 4
6
4
posted @ 2026-06-14 10:47  团爸讲算法  阅读(6)  评论(0)    收藏  举报