洛谷 P1115 最大子段和

前言

感觉时间总是不够用,甚至题目还没有做明白就得写文章(不是

上题目上题目!!

题目

题目分析

这道题目是需要不断取值,在所有满足条件的取值情况中,输出能取到的最大值,只不过要求取值必须是连续的一段子序列而已。怎么样,看到这个题目描述是不是马上想到“给定一定数目的金额,如何才能买到更多的商品”这样类似的问题?那么,在解决这个题目的时候我们就绕不开“贪心”与“动态规划”(背包)了。

代码实现

1.dp

 以个人没做过几题dp的经验来看(那算有个屁的经验啊啊啊),dp的核心特征可能包括以下两点:1.构造一个答案数组用于存储(这实际包含了所有最优解的情况,可以打表进行理解分析)2.有一个十分明显的动态规划递推式(是dp的主体)感觉主要表达的意思就是:生成当前步数最优解并存储在ans数组中,在递推时利用ans数组存储的上一步的最优解继续求最优解。

那么我们就可以用一个十分简洁的代码来实现了。

代码如下:

#include <iostream>
using namespace std;

int main() {
	int ans[200010] = {};
	int a[200010] = {};
	int maxn = -100010;
	int n;
	cin >> n;
	for (int i = 1;i <= n;i++) {
		cin >> a[i];
		ans[i] = max(ans[i - 1] + a[i], a[i]);
		if (ans[i] > maxn)maxn = ans[i];
	}
	cout << maxn << endl;
	return 0;
}

2.贪心

我们可以来想一想,该如何手动实现找到最大子段的和呢?

这连续字段的开头元素,我有三不选:

首先,负数我不选,因为选了不如不选;

其次,上一个元素为正数的正数我不选,因为不是最优解,已经选过了;

最后,第n个元素我不选,因为数组会越界。

(好吧我承认第三点是硬凑的)

ok,我们知道当元素为正数的话,毫无疑问我们会直接选择。接下来的重点是当遇到的元素为负数,我们该如何选择呢?选了,降低我子段的和的大小,不符合题意;不选,万一后面有个大数,再加上这个大数后,我子段的和不减反增呢?

我想到这里的时候思路很乱,直到我发现了这个原则————只要我当前的子段之和加上这个负数元素后仍旧是大于0,说明我到目前为止的子段和仍旧是正贡献,那我不妨忍它一手,将它加上之后继续往后看看,说不定之后发现大数了呢?

那为了防止之后没发现大数反倒亏损的情况,一旦遇到负数元素我们就更新当前的最大值。同时,在当前的子段之和加上负数元素后小于等于0后,说明当前子段已经“无效”了,这时候就可以退出重新开始选取下一个开头元素继续重复全过程了。

看代码!!!

for (int i = 1;i < n;i++) {
	if (a[i] > 0 && a[i - 1] <= 0) {//负数不能作为子段的开头元素
		//若上一个元素为正数也不必计算
		//因为已经包含在上一种情况中了
		ans += a[i];
		for (int j = i + 1;j <= n;j++) {
			if (a[j] >= 0)ans += a[j];
			else {
				//一旦是负数就先更新maxn并开始进行判断
				if (ans > maxn)maxn = ans;
				if (ans + a[j] > 0) {
					ans += a[j];
				}
				//选取负数的可能情况
				else break;
				//否则就直接退出循环
			}
		}
		if (ans > maxn)maxn = ans;//防止第n项为正数但未更新
		//也保证了可以只选一项元素作为最大值
		ans = 0;
	}	
}

写到这我以为已经结束了,然而我有一个地方弄巧成拙了:如果全是负数呢?那么按照我的代码,没有一个元素会被选取,这显然是不合理的。所以被迫滚回去修改了...

以下是完整代码:

#include <iostream>
using namespace std;
//贪心算法
int main() {
	int n;
	int a[200010] = {};
	int ans = 0;
	int maxn = -10010;
	cin >> n;
	for (int i = 1;i <= n;i++) {
		cin >> a[i];
	}
	for (int i = 1;i < n;i++) {
		ans += a[i];
		for (int j = i + 1;j <= n;j++) {
			if (a[j] >= 0)ans += a[j];
			else {
				if (ans > maxn)maxn = ans;
				if (ans + a[j] > 0) {
					ans += a[j];
				}
				else break;
			}
		}
		if (ans > maxn)maxn = ans;
		ans = 0;
	}
	cout << maxn << endl;
	return 0;
}

后记

没怎么看题解,但匆匆看了一眼似乎还能与什么前缀和关联起来...确实水平还不够,明天再花时间好好研究一下吧!

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值