GESP一级的题目,向来喜欢把算法藏在生活场景里。《B4445 [GESP202512 一级] 小杨的爱心快递》就是非常典型的一道:表面上是“送快递”,实际上考的是循环、累加和结果判断。我平时带学生刷真题的时候,发现不少人对“快递”“爱心”这类包装词有天然紧张感,总觉得题目背后藏着什么高级数据结构,结果一读题,发现只是顺序读入n个整数,再算一算总和。这类题非常适合作GESP一级备考的入门练习,也适合家长、老师带着基础薄弱的孩子做“读题能力”训练。下面我从读题、建模、写码、排错到延伸复习,把这套完整过程拆开讲一遍。
考虑到不同渠道拿到的题目细节描述有差异,我按GESP一级最常见的命题风格还原了题目主逻辑:小杨有n件快递,每件快递有一个爱心值,把这些值累加后,判断结果是非负还是负数,分别输出不同结果。考场原题如果存在数据范围或输出措辞上的差别,以你手上的题面为准,核心考点一致。
1. 先把“爱心快递”浓缩成一个可计算模型
1.1 一道典型的GESP一级题长什么样
按GESP一级的常见出题方式,题目描述会先讲一个小故事:小杨的爱心快递站今天收到了n件包裹,每件包裹上都标着一个“爱心值”。有些包裹是正向爱心,比如捐给山区的图书;有些是负向变化,比如运输途中产生了额外损耗。小杨按顺序把这些快递都送完,需要统计最终的爱心总积分,并根据总积分决定是输出“Good luck”还是“Keep going”。
对应的标准输入输出会是这样:
- 第一行输入一个整数n;
- 第二行输入n个整数,分别表示每件快递的爱心值;
- 要求输出总爱心积分;
- 如果总积分大于等于0,则输出“Good luck”;否则输出“Keep going”。
很多初学者看到“快递”两个字,第一反应是“这题是不是要处理地址、包裹类型、配送时间”,那就想多了。一级考试不太可能考复杂模拟,更不会考链式配送算法。它把生活故事外衣剥掉以后,本质就是一个输入处理问题:读入n个数,累加,根据累加结果输出固定字符串。
1.2 把自然语言逐句翻译成代码逻辑
这里我建议所有初学者准备一个“翻译习惯”,拿到题不要急着写代码,先把题面里每一句描述,对应成自己的处理步骤:
| 题目里的生活化描述 | 对应的程序逻辑 |
|---|---|
| 小杨今天有n件爱心快递 | 读取整数n,准备循环n次 |
| 每件快递上标着一个爱心值 | 每次循环读入一个整数a |
| 送完所有快递后的总爱心积分 | 设置累加变量sum,执行sum = sum + a |
| 总积分是否大于等于0 | 用if判断sum >= 0的条件 |
| 输出Good luck | cout或print输出“Good luck” |
| 输出Keep going | 否则输出“Keep going” |
这种翻译的过程,我一般叫“把题目变短”。当你把一段话故事压缩成上面这张逻辑表以后,代码结构基本就浮出水面了。实际写代码时只需要照表执行,不需要分心去记原题里“爱心”“快递站”这些词。
1.3 为什么这题不需要数组
有些同学学到数组以后,看到“n件快递”就下意识想开一个长度为n的数组,把所有爱心值存下来,再统一求和。这种想法本身不算错,但对于这个题来说属于“过度工程”。
原因很简单:本题的最终答案只依赖总和,不依赖某一件快递的单独值,也不需要后面回头访问前面的数据。那我完全可以边读边加,读一个处理一个。数组存下来反而浪费内存,还容易因为下标越界产生运行时错误。
用一句话概括:只有后续还需要用到之前的数据时,才需要数组;只用全体数据的汇总结果时,用累加变量即可。这种“要不要用数组”的判断标准,在一级考试里特别实用,很多孩子直到考完都没想明白,我今天直接把这个标准写出来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 考点拆解:循环、累加器、分支输出的三个高发丢分点
2.1 循环到底应该从0开始还是从1开始
GESP一级的循环题,最常见的一个困扰就是对“从0开始”和“从1开始”的纠结。这道题里,如果采用流式读取,循环次数其实只和n有关,和快递本身编号无关,所以两种写法都行。
cpp复制for (int i = 0; i < n; i++) {
// 读入一个数并累加
}
cpp复制for (int i = 1; i <= n; i++) {
// 读入一个数并累加
}
区别主要在阅读习惯上。新手更习惯i=1到i=n,因为“第1件快递到第n件快递”符合自然语言;老手通常从0开始,因为数组下标默认从0开始。本题没有数组,所以完全看个人喜好。
不过有一个注意点:如果你之后读的不是直接读入整数,而是读入一个包着n个数据的字符串,需要按下标处理,那从1开始还是从0开始会让代码很不一样。建议日常练习时就固定一种习惯,我个人的习惯是:循环次数用0到n-1,纯计数用1到n,两者都要在脑子里区分清楚。
2.2 累加器初始化是所有bug里最常见的
这个坑我要多说两句,因为几乎每次带新人都能看到同一种错误:
cpp复制int sum;
for (int i = 0; i < n; i++) {
int a;
cin >> a;
sum += a;
}
这段代码看起来没毛病,实际上sum没有被初始化。C++中局部变量的初始值是不确定的,可能恰好是0,也可能是某个随机垃圾值。如果环境给了一个正垃圾值,样例可能会意外通过;一旦提交到评测系统,只要初始值不是0,结果全是乱套。
正确的写法是在循环前加一行:
cpp复制long long sum = 0;
Python这边虽然不会出现“未初始化导致随机值”的问题,但同样要注意,累加变量要在进入循环前定义好,不要每一次循环都重新置零。有些同学把sum = 0写在循环体内部,那就等于每次循环都清零,最后只加了最后一个数,输出自然不对。
2.3 分支条件的“等于”最容易看漏
本题要求总积分“大于等于0”输出Good luck,还是“大于0”输出Good luck,两个说法只有一字之差。如果题目原版说的是“不小于0”,那优先考虑总积分恰好为0的情况。考试时如果漏看“等于”,恰好有一组边界数据是总和等于0,就会只错这一个点,分数连样例都对不上,老师还特别难查。
遇到输出字符串的题,还需要注意大小写问题。Good luck中间的l是小写,Keep going里面的k大写,这些细节在评测时通常要求精确匹配。建议遇到这类题,把关键词复制到代码注释里,而不是凭记忆去敲。
3. 参考代码和逐行走查:让第一次提交就尽量一次过
3.1 先写伪代码,再写正式代码
我在教一级备考时,总会要求孩子们先写一遍伪代码,哪怕只花30秒。这个习惯可以极大减少“边想边写”导致的逻辑混乱。这道题的伪代码非常简单:
code复制读入n
sum = 0
重复n次:
读入a
sum = sum + a
输出sum
如果 sum >= 0:
输出Good luck
否则:
输出Keep going
伪代码不需要管语法,写完以后正式代码只是把它翻译成具体语言,出错概率会低很多。
3.2 C++参考实现
cpp复制#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
long long sum = 0;
for (int i = 0; i < n; i++) {
int a;
cin >> a;
sum += a;
}
cout << sum << '\n';
if (sum >= 0) {
cout << "Good luck" << '\n';
} else {
cout << "Keep going" << '\n';
}
return 0;
}
我用了long long而不是int,后面会详细解释原因。这里需要特别说明,代码中cin和cout如果写在一行,比如cin >> n >> a,只要输入时多个数字以空白分隔,读入顺序其实是连续的,不会因为换行就出错。评测系统中的输入以空格和换行作为分隔符,不要求“一个变量独占一行”。
3.3 Python参考实现
python复制n = int(input())
total = 0
for x in map(int, input().split()):
total += x
print(total)
if total >= 0:
print("Good luck")
else:
print("Keep going")
这里把第二行的n个整数一次性读进来,再用map转成整数,最后在for循环里逐个累加。如果一个一个读,可以用for i in range(n): total += int(input()),但那样需要输入时每个数都在单独一行,跟题目常见格式不太一致,所以我更推荐上面这种写法。
Python不需要纠结long long还是int,因为它内置的整数类型可以自动扩展,不会溢出。这也是很多Python考生在一级考试里比较省心的原因。
3.4 手工模拟一次完整运行
代码写完不要急着提交,先在草稿纸上“运行”一遍。用样例输入3 5 -2 8走一遍:
| 循环次数 | 读入的a | sum旧值 | sum新值 |
|---|---|---|---|
| 1 | 5 | 0 | 5 |
| 2 | -2 | 5 | 3 |
| 3 | 8 | 3 | 11 |
循环结束后sum = 11,11大于等于0,所以输出为:
text复制11
Good luck
再看一组负数样例2 -5 -1:
| 循环次数 | 读入的a | sum旧值 | sum新值 |
|---|---|---|---|
| 1 | -5 | 0 | -5 |
| 2 | -1 | -5 | -6 |
循环结束后sum = -6,输出为:
text复制-6
Keep going
手工走查虽然简单,但它能强制你像机器一样去看每一行代码的执行顺序。很多孩子代码报错以后,第一反应是截图问老师,而不是自己拿笔列一遍每步变量的值。如果养成“人肉执行器”的习惯,至少一半低级bug能当场发现。
4. 提交以后才发现:边界数据、溢出和“题面细节”的坑
4.1 数据范围决定变量类型,不是靠感觉
这是我在实际教学中看到丢分最多的地方。很多版本的GESP一级题会把n的范围放到10万,每个a_i的绝对值可能到10的9次方。10万乘10的9次方,总量级是10的14次方。而int类型在32位系统里最大只能表示约21.47亿,也就是2.1乘以10的9次方左右。一旦累加超过这个值,就发生整数溢出。
溢出之后,C++里sum可能会变成一个负数,甚至一个看起来完全随机的数字。如果刚好把所有正数加起来溢出成负数,程序就会错误地输出Keep going。这种情况在本地小范围测试时完全复现不出来,因为小数据没超过int上限。
所以我的建议是:比赛代码里,只要涉及累加求和或相乘,一律优先用long long。虽然考试环境通常默认64位,但养成这个习惯能让你少掉进数据范围的坑。Python用户不需要处理这个,但也要理解C++考生为什么要注意。
4.2 样例通过不代表AC:构造边界用例是必备技能
做题时不能只满足于“样例过了”。评测系统里有隐藏数据,专门检查边界情况。这个题常见的边界用例至少包括这几类:
| 测试类型 | 输入示例 | 期望输出 | 检查点 |
|---|---|---|---|
| 全0 | 3 0 0 0 | 0 / Good luck | 判断等于0时是否能走对分支 |
| 单件正数 | 1 7 | 7 / Good luck | 循环只执行一次是否正确 |
| 单件负数 | 1 -3 | -3 / Keep going | 负数分支是否正确 |
| 总和刚好为0 | 2 5 -5 | 0 / Good luck | 关键是“大于等于0”这个等号 |
| 最大量级 | 100000后接十万个1000000000 | 很大的正数 / Good luck | 验证是否溢出 |
构造这些用例其实花不了几分钟,但对错误定位特别有效。我建议初学者每做一道题,都至少额外测这五类边界,不要只复制题目给的样例。
4.3 输出格式里的“隐藏要求”
这道题如果要求输出两行,第一行是总和,第二行是字符串,那么你必须在总和后面换行。C++中cout << sum << '\n';已经天然处理了换行;Python的print默认也会在末尾换行,所以输出两行时不需要额外拼接转义字符。
还需要注意“行尾是否允许多一个空格”这类问题。大部分评测系统会忽略行尾空格,但严格模式可能不会。尽量保持输出简洁,不输出多余内容。
如果题目只要求输出Good luck或者Keep going,而不需要输出总和,那就要少打一行。这类题面细节最容易被扫读题干的考生漏掉。拿到题目,先把“输出格式”四个字后面的内容完整读两遍,再动手写代码。
4.4 如果题目改成多组测试数据,要怎么处理
一部分GESP级别的题目会加一个T,表示有T组测试数据,每组都是“一个n加n个数”,最后每组各自输出结果。这种情况下,需要在while循环里特别注意累加变量的重置。
cpp复制int T;
cin >> T;
while (T--) {
int n;
cin >> n;
long long sum = 0;
for (int i = 0; i < n; i++) {
int a;
cin >> a;
sum += a;
}
if (sum >= 0) cout << "Good luck\n";
else cout << "Keep going\n";
}
注意看,long long sum = 0;写在了while循环内部。这样每一组数据开始前,sum都会被清空。如果把它写在while外面,那么第二组数据会从上一组的结果继续累加,输出必然错。这是一个非常经典的多组数据处理陷阱。
4.5 排查“答案错误”时的顺序
如果提交后显示答案错误,我的排查顺序是:第一,重新读题,看输出格式和分支条件是否理解反了;第二,检查变量名是否抄错,尤其是把n当成a、把a当成n;第三,检查是否忘了初始化;第四,检查数据类型是否溢出;第五,手工构造边界数据跑一遍。
按照这个顺序,绝大多数问题都能在十分钟内定位。最忌讳的是“觉得没问题”然后反复提交同一份代码,那样既浪费时间,也看不出问题。
5. 从“爱心快递”延伸:GESP一级的备考节奏和自查清单
5.1 一级题到底在考什么
GESP一级与二级的区别,核心在于“是否使用数组/简单排序”这类结构。一级更偏向于顺序结构、分支结构、单层循环、简单字符串处理。具体到这套题,考点就是:
- 循环读入不定数量的整数
- 累加器的使用
- 分支判断
- 固定字符串输出
- 数据范围与变量类型选择
如果你能把“爱心快递”这道题完整不靠提示地写出来,那你在理解一级核心考点上已经没有大问题。它考得并不偏,反而非常适合用来检验“基础地基”是否稳固。
5.2 把每道题都变成“四步练习”
我带学生做真题时,不管题目多简单,都要求走四步:第一,读题后不看代码,先口述一遍题目的输入和输出是什么;第二,在草稿纸上手算题目给的样例,搞清楚样例结果怎么来的;第三,写伪代码,再翻译成正式代码;第四,提交前构造至少3组边界数据。四步做完,才算真正完成一道题。
这种训练方式初期比较慢,一道简单题可能要花20多分钟。但坚持十道题以后,速度会明显上来,而且正确率特别稳定。学习编程和跑步一样,前期把动作拆细,后面才能在赛道上跑得更顺畅。
5.3 用GESP历年一级题做同类训练
学完“爱心快递”以后,可以去找同级别的其他一级真题做巩固,比如往年出现过的“小猫分鱼”类问题,还有与消除、报数、储蓄等生活场景结合的题目。我见过的GESP一级题大多遵循相同逻辑:一个生活故事,一个整数输入,一段循环判断,一个固定输出。
做这类题时,尽量限定时间。正式考试时一道一级题如果超过25分钟还没完整通过,就要果断调整节奏,先放下,回头检查已经做过的题目,确保会做的题不丢分。
5.4 通用的自查口诀与一个简单模板
最后分享我一直用的自查习惯:变量先初始化,循环次数对得上,边界数据单独测,输出格式照着抄。这四句话看起来像口号,但每次提交前按照顺序点一遍,真能拦住大部分低级错误。
另外,写代码时可以先在心里搭建一个万能模板:
cpp复制#include <iostream>
using namespace std;
int main() {
// 1. 读入数据
// 2. 按题意处理
// 3. 输出结果
return 0;
}
不管题目场景怎么变,输入、处理、输出这个三段式框架都不会变。无论是“爱心快递”还是别的包装名,只要把生活化描述翻译回这三个阶段,GESP一级的模拟题基本上就稳了。
以我批改大量代码的经验来看,这道题真正难住大家的往往不在算法,而在于读题太快、忽略边界、忘记初始化、输出格式没看清。把这个“送快递”的小题当成一次完整的工程流程来对待,慢慢你会发现,一级考试里这类题目其实是送分题。
