题解:学而思编程 最大子段和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
【核心思想】
-
问题分析:给定长度为 \(n\) 的序列 \(a_1, a_2, \ldots, a_n\),需要回答 \(m\) 个查询:对于每个查询位置 \(pos\),求子数组 \(a[pos..n]\) 的最大子段和。这是一个后缀区间最大子段和查询问题,需要预处理来支持 \(O(1)\) 查询。
-
算法选择:
- 后缀DP预处理:\(dp[i]\) 表示从位置 \(i\) 开始到 \(n\) 的最大子段和
- 两次DP:第一次计算以 \(i\) 开头的最大子段和,第二次计算区间 \([i, n]\) 的最大子段和
-
关键步骤:
- 第一次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)\)
- 第一次DP(从后向前):计算以 \(a[i]\) 开头的最大子段和
-
时间/空间复杂度:
- 时间复杂度:\(O(n + m)\),预处理 \(O(n)\),每次查询 \(O(1)\)
- 空间复杂度:\(O(n)\),存储 \(dp\) 数组和原数组
-
后缀预处理 + 区间查询的核心思想:
- 离线预处理:通过两次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
浙公网安备 33010602011771号