上链接:https://www.luogu.com.cn/problem/P1115
上题干:
题目描述
给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个整数,表示序列的长度n。
第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai。
输出格式
输出一行一个整数表示答案。
输入输出样例
输入 #1
7 2 -4 3 -1 2 -4 3输出 #1
4说明/提示
样例 1 解释
选取 [3,5] 子段 {3,−1,2},其和为 4。
数据规模与约定
- 对于 40%40% 的数据,保证 n≤2×10^3。
- 对于 100%100% 的数据,保证 1≤n≤2×10^5,−10^4≤ai≤10^4。
这道题的思路很简单,它既然要求我们在这一段长区间里找出一段子区间,使得子区间的和最大。
那么我们就把所有的子区间的长度求出来。在里面找到最大的子区间不就好了吗。
所以我们直接能想到的做法应该是暴力枚举区间的两个端点。
比如搞两个指针,左指针最开始是指向a[0],然后右指针不断向右移动,来枚举以a[0]为原点的所有子区间。然后不断维护最大值就好了。
这样做简单易懂,就是时间复杂度为O(n^2). 无法通过本题。
假如我们能够只循环一次就找出最大值就好了。
所以这题需要用到动态规划。
动态规划是什么呢?
- 是解决策过程的最优化,避免重复计算浪费时间
- DP问题的两个特点:最优子问题,重复子问题
- 过程:定义最优解 - 构造最优结构 - 自底向上计算子问题 - 得出结果
正常的做法都是以a[0]为起点不断扫描后面,那么我们是不是可以反其道而行之,以每个a[i]为终点,自底向上计算最大值呢?
从a【0】开始,以a【0】为终点的话,这个区间的最大值就是a【0】
以a【1】为终点,那么以a【1】为终点的子区间内的最大值可能是 a【0】+a【1】 or a【1】;
(注意,我们是以a【1】为终点,没说以哪个点为起点,所以必须要含有终点a【1】,前面有没有主要看条件满足否,这样才能全部扫描到所有的最大字段)
dp要有递推公式,所以我们抱着求递推公式的方向去寻找,将其化为通式,即:
以a【1】为终点的区间最大值 为 上一个区间的最大值+a【1】 or a【1】
那么设以a【i】为终点的区间最大值是f【i】,所以就有f【i】=max(f【i-1】+a【i】,a【i】);
然后直接递推就好了,最后给这个数组排个序,输出最大值就行了。
所以,dp不是凭空想象出来的式子,而是,我们要抱着求递推公式的方向去寻找。
并且以后我们想到的暴力枚举,如(以某点为起点,遍历其终点) 我们都可以试着倒过来想,以某点为终点,从下至上计算子问题。
最后我们上代码:
const int N = 2e5 + 10;
int a[N];
int f[N];
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i++)cin >> a[i];
f[0] = a[0];
for (int i = 1; i < n; i++)
{
f[i] = max(f[i - 1] + a[i], a[i]);
}
sort(f, f + n);
cout << f[n - 1];
}