题解:洛谷 P1115 最大子段和
【题目来源】
【题目描述】
给出一个长度为 \(n\) 的序列 \(a\),选出其中连续且非空的一段使得这段和最大。
【输入】
第一行是一个整数,表示序列的长度 \(n\)。
第二行有 \(n\) 个整数,第 \(i\) 个整数表示序列的第 \(i\) 个数字 \(a_i\)。
【输出】
输出一行一个整数表示答案。
【输入样例】
7
2 -4 3 -1 2 -4 3
【输出样例】
4
【核心思想】
-
问题分析:给定长度为 \(n\) 的序列 \(a_1, a_2, \ldots, a_n\),需要找到一个连续非空子数组 \(a[l..r]\),使得其和 \(\sum_{i=l}^{r} a_i\) 最大。这是一个经典的线性动态规划问题,具有最优子结构性质。
-
算法选择:
- 动态规划(DP):定义状态 \(f[i]\) 表示以第 \(i\) 个元素结尾的最大子段和
- 贪心决策:对于每个位置,决定是将当前元素加入之前的子段,还是重新开始一个新的子段
-
关键步骤:
- 定义状态:\(f[i]\) 表示以 \(a[i]\) 结尾的最大子段和
- 状态转移方程:\[f[i] = \max(f[i-1] + a[i], a[i]) \]
- \(f[i-1] + a[i]\):将 \(a[i]\) 接在以 \(a[i-1]\) 结尾的最大子段后面
- \(a[i]\):从 \(a[i]\) 开始一个新的子段
- 初始化:\(f[1] = a[1]\)(第一个元素只能单独成段)
- 遍历计算所有 \(f[i]\),最终答案为 \(\max_{1 \leq i \leq n} f[i]\)
- 空间优化:可以使用滚动数组,只保留 \(f[i-1]\),空间复杂度降为 \(O(1)\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),只需遍历数组一次
- 空间复杂度:\(O(n)\),使用 DP 数组(可优化至 \(O(1)\))
-
线性DP与Kadane算法的核心思想:
- 最优子结构:以 \(a[i]\) 结尾的最大子段和,只与以 \(a[i-1]\) 结尾的最大子段和有关
- 局部最优推全局最优:通过维护每个位置的"以该位置结尾的最大子段和",最终得到全局最大值
- 贪心选择:当之前的子段和为负数时,放弃它重新开始,因为负数会拖累后面的和
- 适用于最大子矩阵、最大子数组变种等问题
【算法标签】
普及- #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 200005; // 定义最大数组长度
int n; // 数组元素个数
int a[N]; // 存储原始数组
int f[N]; // 动态规划数组,f[i]表示以a[i]结尾的最大子数组和
int ans = -1e9; // 存储全局最大子数组和,初始化为极小值
int main()
{
// 输入数组元素个数
cin >> n;
// 输入数组元素
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 动态规划:计算以每个位置结尾的最大子数组和
for (int i = 1; i <= n; i++)
{
// 状态转移方程:
// 以a[i]结尾的最大子数组和 = max(前一个最大子数组和 + a[i], 单独取a[i])
f[i] = max(f[i - 1] + a[i], a[i]);
}
// 遍历所有位置,找到全局最大值
for (int i = 1; i <= n; i++)
{
ans = max(ans, f[i]);
}
// 输出最大子数组和
cout << ans << endl;
return 0;
}
【运行结果】
7
2 -4 3 -1 2 -4 3
4
浙公网安备 33010602011771号