前言
感觉时间总是不够用,甚至题目还没有做明白就得写文章(不是
上题目上题目!!
题目
题目分析
这道题目是需要不断取值,在所有满足条件的取值情况中,输出能取到的最大值,只不过要求取值必须是连续的一段子序列而已。怎么样,看到这个题目描述是不是马上想到“给定一定数目的金额,如何才能买到更多的商品”这样类似的问题?那么,在解决这个题目的时候我们就绕不开“贪心”与“动态规划”(背包)了。
代码实现
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;
}
后记
没怎么看题解,但匆匆看了一眼似乎还能与什么前缀和关联起来...确实水平还不够,明天再花时间好好研究一下吧!