1003 Max Sum

我嘞个去,csdn 是有新手保护期的吗,中午发第一篇晚上就有300+浏览量在这里插入图片描述
有些懵啊,第四题又卡了好长时间。。
倒是头一次遇到 Time Limit Exceeded。。。

题目描述:
在这里插入图片描述
或许做得少了,觉得题目好怪,也没说清“子列”到底是个啥,我的理解是:顺序且不能漏,个数范围是1~n。



我的做法

所以对每一行 input,我直接穷举出所有子列的和,并取最大值,要点如下:

1、对每个子列的求和结果 sum_i,用结构体(或类)存储sum, begin, end,以便最终输出时取用 beginend

2、将子列按元素个数 i 来分,分别加和,即元素个数为 i 的子列有 n - i + 1 个。故有三重循环:
  - 第一重自不必说,读入 T 行,并且存储到 input 向量中;
  - 第二重,子列元素个数从 n → 1;
  - 第三重,加和。

以下是初始代码至少我觉得是能输出正确答案的,不过超时了:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;


struct SumI
{
    int begin;
    int end;
    //int len;
    int sum;
};


int main() {
    int T;
    cin >> T;
    for (int i = 0; i < T; i++) {
        int n;
        cin >> n;
        vector<SumI> maxes;        // 所有子列和的最大值
        vector<int> input;

        int a;
        for (int i = 1; i <= n; i++) {
            cin >> a;
            input.push_back(a);
        }
        // 数量为 i 的子列(i 从 n 到 1)
        for (int i = n; i >=1 ; i--) {
            vector<SumI> sums;        // 数量为i的k子列,和的最大值
            // 有 n - i + 1 个和,每个和要加 i 次
            for (int j = 1; j <= n - i + 1; j++) {
                SumI sum_i;
                sum_i.begin = j;
                sum_i.end = j + i - 1;
                sum_i.sum = 0;
                // k 控制加的次数为 i,m 控制加的起始位置为 j
                for (int k = 1, m = j; k <= i; k++, m++) {
                    sum_i.sum += input[m - 1];
                }
                sums.push_back(sum_i);
            }
            // 使用 std::max_element 找到最大值
            auto max_iter = max_element(sums.begin(), sums.end(),
                [](const SumI& a, const SumI& b) {
                    return a.sum < b.sum;
                });

            // 判断向量是否为空,避免 dereferencing 空指针
            if (max_iter != sums.end()) {
                SumI max_i = *max_iter;
                maxes.push_back(max_i);
            }
            sums.clear();
        }

        auto max_iter = max_element(maxes.begin(), maxes.end(),
            [](const SumI& a, const SumI& b) {
                return a.sum < b.sum;
            });

        if (max_iter != maxes.end()) {
            SumI max = *max_iter;
            // 输出
            cout << "Case " << i + 1 << ":" << endl;
            cout << max.sum << " " << max.begin << " " << max.end << endl;
            if (i != T - 1) {
                cout << endl;
            }
        }
        maxes.clear();
    }
    return 0;
}

上述代码超时了(不超也难啊,直接穷举了我去),相对于这个题目序号来说太长太繁琐(主要是求最大值比较占地方,还用了两次,可以直接封装成函数),不过思想很简单,也就i, j, k, m一堆需要弄明白啥意思。


借鉴代码

代码摘自:https://blog.csdn.net/m0_52072919/article/details/115026413

#include <iostream>
#include <cstdio>
using namespace std;

int main() {
    int T;		// input行数
    cin >> T;

    for (int i = 0; i < T; i++) {
        // 因为所有数都在 -1000 ~ 1000 之间,子列和的最大值最小也不会小于 -1001
        int maxsum = -1001;
        int begin = 0;
        int end = 0;
        int temp = 1;        // 临时记录位置

        int n;
        cin >> n;
        int sum = 0;
        // 这种写法是后面覆盖前面,所以从1开始,不同于下面是将所有结果拿来做比较,相等时就取前面的
        for (int j = 1; j <= n; j++) {
            int a;
            cin >> a;
            sum += a;
            if (sum > maxsum) {
                maxsum = sum;
                begin = temp;
                end = j;
            }
            if (sum < 0) {
                sum = 0;
                temp = j + 1;
            }
        }
        printf("Case %d:\n%d %d %d\n", i + 1, maxsum, begin, end);
        if (i != T - 1) {
            cout << endl;
        }
    }

    return 0;
}

这段代码就非常简练,我看了得有一两个小时。。。很神奇,核心代码就是两个 if 判断,跑一下竟然真的完成了题目要求,觉得作者对题目的规律有较多思考才能写得出

e.g.
在这里插入图片描述

理解:

  • 当且仅当 sum > maxsum 时才会更新 maxsum 和其对应的 begin, end,其他情况 sum 变化是不会影响到 maxsum 的;
  • 从读入第一个数开始(begin = 1maxsum = sum = a),每读一个数,只有 非负(以正理解) 和 负 两种可能,读入正 sum 值就增大,读入负 sum 值就减小,以==“打包”==的思想来看,多个正值和负值只要和 >0,就可看做是maxsum 一部分,所以读入负没关系,只要 sum 没有小于0就仍是 maxsum 的一部分, begin 位置就不会变。
  • 所以 maxsum 是排除掉两边“负的区域”,其包含的均是“正的区域”;
  • sum < 0 时,说明该区域(从当前的 beginj)是负值,从下一位(j + 1)开始重新开始 sum 的计算。又说到第一个,这一切并不影响 maxsum,仅 sum > maxsum 时才会更新,所以 maxsum 始终表示曾出现的最大值。

好吧,我承认自己说的像是一坨,表达能力实在有限,或者说自己理解的也不是很透彻,希望多做几道题后能表述的更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值