[pieces]吃甘蔗——两端定个数元素和最大问题

题目

    • 题目描述
    • 输入
    • 输出
  • 几种思路
    • 前缀和法
    • 三种仅求解段和的方法
      • 在数组两端滚动
      • 拼接数组中段法
      • 单中段互补法
  • some trivia

题目描述

1根甘蔗共有N段,美味度为a[1…N],Sophie想要吃掉其中L段,并且甘蔗只能从2头开始吃。
求Sophie能获得的最大总美味度。

输入

第一行2个正整数N,L
第二行N个整数a[i]

输出

输出1个正整数,代表能获得的最大总美味度

几种思路

首先要明确一点,就是必须使用long long,否则越界是必然的。

前缀和法

这个方法上OJ不能AC。虽然在小样本测试下正确。猜测是给定的数据量过大或者数据过大,导致(不正确地)数值溢出。

#include 
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 
#include 
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
#include
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 
#include 
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函数

你可能感兴趣的:(C/C++,#,递推)