1. 先说说我为什么会给一道求和题单开一页tracker
那天晚上照例打开牛客的每日一题,弹出来一道名字很朴素的题——“many sum”。我当时的心理活动和大多数刷题人会做的事一模一样:求和题嘛,套个循环加起来不就完了?结果提交上去,超时。再一看数据范围,好家伙,n和m的数量级都不小,每次询问都老老实实地把区间扫一遍,复杂度直接几百万起步,这题要是能过才见鬼了。
那段时间我刚好在坚持做牛客的每日一题,顺手把所有刷过的题都记在一个tracker表格里。起初tracker上只是记录日期、题号、过没过,后来发现光记这些根本没有复盘价值,因为隔两周回去看自己写过的AC代码,经常想不起来当时为什么这么做。我开始在tracker里补充知识点、做题耗时、提交失败次数、当时卡住的原因,再后来甚至给每道题拍了“总结照片”,就是那种一句话核心思路加一段伪代码的小模板。
“many sum”就是我第一次认认真真在tracker里单独开了一页的题。不是因为这道题有多难,而是它太典型了,典型到值得把它从“每日一题打卡记录”里单独拎出来,当作一类题的代表好好拆一遍。它的题目逻辑其实非常干净,但涉及的基础知识点、输入输出处理、边界条件和优化思路,几乎覆盖了算法入门阶段最常见的所有坑。如果你也是刚开始刷牛客、打算用每日一题培养手感但又不知道怎么整理学习成果的人,这篇就当是我把那一页tracker内容翻出来,对着你从头讲一遍。
我用的追踪方式不复杂,甚至可以说不高科技,就是一张Markdown表格加一个简单的统计脚本。但正是这张“很土”的表,让我后来发现了一个反直觉的事实:做了几百道题之后,真正阻碍我的不是难题不会解,而是同一类基础题的细节反复犯错。比如区间求和题里的long long、下标偏移、多组输入,全都是老朋友了,可它们每次都能换个姿势让我重新WA一次。所以我才坚持把每道题的知识点标签、失败原因、一句话结论都记下来,让tracker不只回答“我刷了多少题”,还能回答“我又在哪些地方栽过”。
很多刷题的人喜欢追求题量,觉得一天刷五道才叫努力。我的经验刚好相反——一天一道题,配合认真记录和定期复盘,长期效果比盲目堆量好得多。就像给设备刷系统固件一样,每天一个小增量更新,攒一段时间就是个稳定版本;如果每天灌一堆乱糟糟的补丁,迟早要出问题。牛客每日一题这个入口的好处在于题目质量比较稳定,不会今天冒出一道水题明天来一道劝退题,适合当作长期迭代的素材。而tracker就是你的版本管理工具,帮你看清每次“提交版本”里到底改了什么、为什么改。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. “many sum”到底在考什么:题面拆解与核心思路
2.1 题目到底说了什么
先按我记忆中的题面结构说个大概,它核心就是给你一个长度为n的数组,再给你m次询问,每次询问一对区间左右端点l和r,要求输出子数组从第l个元素到第r个元素的和。题目名字叫“many sum”其实已经很直白了——求和,而且求很多次。这里的“many”就是m很大,大到不允许你每次询问都重新加一遍。
这类题在牛客和各种OJ上都有变体,有的会把数组初始值换成别的公式生成,有的会要求结果对某个数取模,但内核没变:静态数组、多次区间求和。如果你在牛客每日一题里碰到它,大概率也是这个套路,只是数据范围可能换一换。
第一次做的时候我犯了个经典错误:看到题就写俩嵌套循环,外层处理m次询问,内层从l遍历到r累加。本地测试样例过了,心里美滋滋,一提交直接TL。原因很简单——如果n是10^5的数量级,m也是10^5的数量级,最坏情况每次询问都横跨大半个数组,总操作量就到了10^10级别,往好了说跑十几秒,往坏了说永远跑不完。
2.2 前缀和为什么能一次搞定
这道题真正的考点是前缀和。理解起来非常直白:先用一次遍历预处理出一个前缀和数组pre,pre[i]表示原数组从第1个元素加到第i个元素的总和。那么从l到r的子数组和就等于pre[r]减去pre[l - 1]。为什么是l - 1而不是l?因为前缀和是包含当前下标的,pre[r]里已经包含了第l到第r个元素,我要去掉的是第1到第l - 1个元素,pre[l - 1]刚好就是这个范围的总和。
有了这个预处理,每次询问就变成了两次读取加一次减法,O(1)时间出结果。整体复杂度从O(n * m)降到了O(n + m)。这就是这道题从超时到AC的最后
