题目描述
1根甘蔗共有N段,美味度为a[1…N],Sophie想要吃掉其中L段,并且甘蔗只能从2头开始吃。
求Sophie能获得的最大总美味度。
输入
第一行2个正整数N,L
第二行N个整数a[i]
输出
输出1个正整数,代表能获得的最大总美味度
几种思路
首先要明确一点,就是必须使用long long
,否则越界是必然的。
前缀和法
这个方法上OJ不能AC。虽然在小样本测试下正确。猜测是给定的数据量过大或者数据过大,导致(不正确地)数值溢出。
#include <iostream>
using namespace std;
#define MAXN (int)1e5 + 20
long long n, l;
long long a[MAXN], b[MAXN];
void init()
{
cin >> n >> l;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
b[i] = b[i-1] + a[i];
}
}
void solve()
{
long long ans = 0, tmp = 0;
for (int i = 0; i <= l; i++)
{
tmp = b[i] + b[n]-b[n-l+i];
if (tmp > ans)
ans = tmp;
}
cout << ans << endl;
}
int main()
{
init();
solve();
}
三种仅求解段和的方法
以下三份代码来自巨佬:核心思想都是不能求算一个总体的前缀和数组。
在数组两端滚动
//Allen Lee
#include <iostream>
#include <algorithm>
using namespace std;
void solve()
{
int n, l, case_num = 1;
cin >> n >> l;
long long singleBite = 0;
long long sugarCane[100005] = {0}, bites = 0;
for (int i = 0; i < n; i++)
{
cin >> sugarCane[i];
if (bites < l)
{
singleBite += sugarCane[i];
bites++;
}
}
long long res = singleBite;
for (int i = 1; i <= l; i++)
{
singleBite -= sugarCane[l - i];
singleBite += sugarCane[n - i];
if (singleBite > res)
{
res = singleBite;
}
}
cout << res << endl;
return;
}
int main()
{
solve1();
return 0;
}
拼接数组中段法
将原组复制两遍拼接在一起,那么法一中的两头求解转化成邻接求解。有一个有趣的问题在于,使用vector.resize()会大大节省数组的无必要的空间占用
//Neptune Yang
#include<iostream>
#include<vector>
using namespace std;
int main()
{
long long max = 0, sum = 0;
int N, L;
cin >> N >> L;
vector<int> a;
a.resize(2 * N);
for (int i = 0;i < N;i++)
cin >> a[i];
for (int i = 0;i < N;i++)//将原组复制两遍拼接在一起,那么法一中的两端滚动就变成了邻接段的问题
a[N + i] = a[i];
for (int i = 0; i < L; i++)
{
sum += a[2 * N - 1 - i];
max = sum;
}
for (int k = 0;k < L;k++)
{
sum += a[N + k] - a[N - L + k];
max = (sum > max) ? sum : max;
}
cout << max;
}
为了发挥这种连续结构的优势我们还可以对中间对使用“左右指针”,从而使万级的数字加减转换为“自加自减".
int pl = n-l, pr = n;
long long sum = 0;
for (int i = pl; i < pr; i++)
sum += v[i];
long long ans = sum;
while (pl <= n)
{
sum += (v[pr++]-v[pl++]);
if (sum > ans)
ans = sum;
}
单中段互补法
其实这上面两个方法已经很好了,但是由于测试数据的设置仍然不太清楚。不能确定方法之间的优劣。
然而事实上这道题普遍选取比较多的节数,所以最终的结果是滚动选取中间段落再用总体减,会表现出更好的性能。
就是只用单个的原有数组,取中段(即不选取的段落)进行求和滚动。注意一个有趣的优化:max函数在比较过程当中,传递的是引用,所以不管是直接使用内置类型,还是使用初始化列表对于一群数据,相对于先计算再分支、赋值,或者使用算两次(这个题的longlong算两次实在要命)的三目运算,都不如移动来的快。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, l;
vector<int> num;
long long ans = 0, tmp = 0, sum = 0;
cin >> n >> l;
num.resize(n);
for (int i = 0; i < n; ++i)
{
cin >> num[i];
sum += num[i];
if (i == n-l-1)
tmp = sum;
}
ans = sum - tmp;
for (int i = 0; i < l; ++i)
{
tmp += (num[i + n - l] - num[i]);
ans = max(sum - tmp, ans);
}
cout << ans << endl;
return 0;
}
some trivia
- 不要尝试列举一个过大的数组。不论是内容还是个数
- vector比C数组、string比C字符串的优越性不是一星半点
- 分支快于三目运算符,快于max函数。
- 一次比较时使用分支(由于不太稳定,有时三目也是很不错的选择)
- 有计算(尤其像这个题数据比较大)的时候,传参会相对较慢,所以应该使用max函数