童年就是,你长大了以后,会怀念的小时候。————通过洛谷1115 最大子段和 来讲述应该什么时候使用dp

上链接: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];
	
}



  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值