最近在带几个学生准备GESP一级考试,刷到2023年9月这套真题的时候,B3863这道“买文具”让我印象挺深。可能有人会说,这不就是一道最基础的计算题吗,有什么好讲的?但恰恰是这种看起来“过于简单”的题,最能暴露初学者在输入输出、变量类型、运算顺序上的问题。如果你正准备考一级,或者刚开始接触编程,认真吃透这道题,比你盲目刷十道难题更有价值。
我自己的经验是,每次带学生,第一节课不会急着讲花哨的算法,而是先让他们把这类“读完题就能列算式”的题写到滴水不漏。因为编程这件事,语法是壳,能把现实问题翻译成计算机能理解的步骤,才是真正的内核。“买文具”就是这样一个绝佳的练习素材:它场景贴近生活,逻辑直白,却能串起好多基础知识点。下面我把这道题的拆解思路、双语言代码、坑点和备考方法一次说完。
1. 这道题究竟在考什么
1.1 题目大意看清了吗
这道题的常规版本是这样:某同学去文具店买文具,店里卖笔记本、铅笔、橡皮三种商品。输入三行,每行两个整数,分别表示对应文具的单价和购买数量,要求计算总价并输出。如果题面在具体文具种类或输入格式上有细微调整,解题逻辑完全一样,核心始终是“单价乘以数量,再求和”。
我第一次让学生做的时候,好几个人读题太快,以为只算一种文具的钱,输出的时候直接漏掉了后面的输入。这其实不是编程能力问题,而是读题的耐心问题。竞赛题的第一关,从来不是写代码,而是读懂题目到底要你干什么。我的建议是拿到题先别碰键盘,至少把题面读两遍:第一遍通读,第二遍圈出所有数字和单位。很多所谓“马虎”丢的分,根源都是这一步没做到位。
从题面看,输入包含三组数据,每组是“单价、数量”,输出的就是一个整数总价。如果觉得自己没看清题,我教你一个最笨但最有效的办法:把输入样例手算一遍。比如笔记本3元买2本、铅笔2元买5支、橡皮1元买3块,先用手算出总价19,再去看输出样例是什么。对上了,说明你的理解没问题;对不上,赶紧回头重新读题。这个习惯我从学编程一直用到现在,哪怕刷高阶题也一样管用。
1.2 一级考试到底想考察什么
拿到一道真题,不要只看代码量,还要看它背后的考点。GESP一级的考试范围,主要覆盖变量定义、基本数据类型、输入输出、算术运算、顺序结构、简单的条件判断和循环。这道“买文具”题,最核心的知识点就三个:
- 变量:能用合适的变量类型存储整数数据
- 输入输出:能正确读取键盘输入、按格式打印结果
- 算术运算:能写出单价乘以数量的表达式并累加
说实话,这三个知识点单独拆开都不难,但放在同一道题里,就形成了一道检验基础的“组合题”。一级就是要把这种组合练到条件反射的程度。你看到“买文具”,脑子里应该立刻蹦出两件事:第一,要定义多少个变量;第二,最终输出的是整数还是要考虑小数。这两个问题想清楚,代码的大框就出来了。
另外,这道题还隐藏了一个容易被忽略的考点:数据的组织方式。三行输入看起来是分开的,但从程序处理的角度,它们是一组一组的“单价、数量”对。能不能从题面里抽象出这种结构,决定了你后面学数组、学结构体时会不会吃力。所以别小看一级题,它是在帮你建立初始的“数据结构感觉”。就像学写字要先练横平竖直,程序里的每一个输入输出动作,都是在给将来的复杂逻辑打地基。
1.3 为什么说这是必拿分的基础题
在考试里,这类题通常放在编程题靠前的位置,分值就是给认真准备的人送的。我见过不少学生,考完出来说“哎呀最后一题没写出来,但第一题应该没问题”,结果分数出来不是那么回事。问题就出在:越简单越容易掉以轻心。尤其这种计算题,很多人觉得“不就是乘法吗”,于是变量名乱写、输入顺序搞反、输出多了提示文字,各种低级错误全跑出来了。
根据我的经验,“买文具”这道题的得分率并不像想象中那么高。原因无非是这几个:输入处理出错、变量名写错、输出格式不对,甚至有些同学直接在草稿纸上排算式,代码却写得七零八落。所以我的建议是,这种题不但要做对,还要做得“稳”。什么叫稳?就是同一个代码,今天写是对的,明天考试写也是对的;本地运行对,提交到评测系统也对。要达到这个程度,靠的不是考前临时抱佛脚,而是平时对每一个基础动作都较真。把这种必拿分的题稳稳收进口袋,你在考场上的心态会完全不一样,后面的难题也能沉下心去思考。
2. 从生活问题到代码逻辑:解题思路拆解
2.1 读题四步法:找输入、找输出、找计算、找边界
我一般会建议初学者用“读题四步法”来处理这类简单题,步骤固定下来以后,读题就不会丢三落四。这个方法不只是针对这一道题,几乎所有入门级题目都能套进去。
第一步,找输入。题目说“输入三行,每行两个整数”,那你就要明确:程序要从标准输入读6个整数。读入的顺序是谁先谁后,必须和题面完全一致。很多低级错误就出现在这里,你以为先读单价,结果题面是先给数量。一个稳妥的技巧是,在草稿纸上把输入样例抄一遍,然后在每个数字下面标出它对应的变量名,写代码时对照着来。
第二步,找输出。题目要求“输出一个整数,表示总价”,那你就要知道输出应该是一行、一个数字、末尾有换行。是整数,就不要输出小数;是一个数字,就不要输出多余提示文字。评测系统是按标准输出逐字符比对的,多一个空格、少一个换行,都可能导致程序被判错。
第三步,找计算。这题的计算是“单价乘以数量”,三种文具分别算完再求和。把算式写出来:总价 = 笔记本单价 × 笔记本数量 + 铅笔单价 × 铅笔数量 + 橡皮单价 × 橡皮数量。能列出这个算式,代码就完成了一半。列算式的时候,最好把每一步的中间结果也写出来,这样写代码时你能随时对照检查。
第四步,找边界。一级题通常不考特别刁钻的边界,但你要留意数据范围。比如单价和数量是不是正整数、有没有可能超过int的范围。养成读数据范围的习惯,是从一级开始就必须训练的意识。后面到了二三级,输入数据里经常藏着陷阱,比如负数和极大值,一级就养成这个意识会省很多事。
2.2 总价计算的两种实现思路
面对“多组单价、数量求和”这样的问题,基础阶段有两类写法。第一种是把所有输入读进来,用一条表达式算完。比如读完6个整数以后,直接写 total = price1 * count1 + price2 * count2 + price3 * count3。这种写法直观,和算式一一对应,适合数据组数确定、数量少的场景。优点是逻辑清晰,缺点是如果文具种类变成100种,表达式会写到怀疑人生。
第二种是用一个累加变量,循环读入每一组数据,边读边加。也就是先设置 total = 0,然后每次读到一组单价和数量,就执行 total = total + 单价 × 数量。这种写法更接近真实生产代码的习惯,因为现实中的输入行数往往是可变的,不可能在代码里提前写死。虽然一级考试还未必要求用循环,但我建议有心挑战满分的学生把这种写法也练一练。
对于这道题,两种方法都能过。但在学习阶段,我建议你先把第一种写熟练,再切换成第二种。为什么?因为第一种能帮你验证自己对算式的理解,第二种则帮你建立“累加器”这个重要编程思想。累加思想后面会在求和、计数、统计这些题目里反复出现,早一点接触,后面学循环时会顺畅很多。实际上,很多二级题目就是在“买文具”这个模型上不断加料,先打牢底子,后面就是水到渠成的事。
2.3 伪代码:把思路变成步骤
写代码之前,先在纸上写伪代码,是我特别推荐初学者养成的习惯。尤其考试的时候,思路容易乱,伪代码就是你的定心丸。伪代码不要求语法规范,只要你自己能看懂就行,它的作用是逼你把思路往前推一步。
以累加器的思路为例,伪代码可以写成这样:
total = 0 重复3次: 读入 price, count total = total + price * count 输出 total就这么几行。写完后你会发现,代码翻译工作变得机械而简单。很多学生跳过了这一步,直接上手写代码,结果往往是这里少个变量、那里多个符号,越改越乱。伪代码的价值在于,它把“想”和“写”这两件事分开了:先在脑子里把方案想清楚,再进入语法层面去落实。
有人觉得写伪代码浪费时间,我觉得恰恰相反。这道题简单,可能十分钟就写完代码了,伪代码只占两分钟。但等你学到二三级,面对带条件、带循环、甚至带嵌套的题目,伪代码能帮你节省的远远不止二十分钟。我见过太多学生在考场上改了又改,最后发现思路起点就是错的,如果先写几行伪代码,完全可以避免这种惨案。
3. 双语言实现:C++ 与 Python 逐行拆解
3.1 C++ 写法与常见细节
GESP一级考试支持C++和Python两种语言,我周围的学生大部分选的是C++,毕竟后面想走竞赛路线的话,C++是绕不开的。先看C++的完整解法:
#include <iostream> using namespace std; int main() { int price1, count1; int price2, count2; int price3, count3; cin >> price1 >> count1; cin >> price2 >> count2; cin >> price3 >> count3; int total = price1 * count1 + price2 * count2 + price3 * count3; cout << total << endl; return 0; }我来逐段解释。第一行的#include <iostream>是为了使用输入输出流,using namespace std;让cin和cout可以直接使用,不用写成std::cin。对一级来说,这两行直接背下来没问题,但最好知道它们是在干什么。main函数是程序入口,C++程序从main的第一条语句开始执行,最后return 0;表示正常结束。
int用来声明整数变量。这里的变量名我用的是price1、count1这种带编号的命名方式,用意是让代码读起来一目了然:第一个文具的单价是多少、数量是多少。新手容易犯的错误是变量名乱取,比如a、b、c、d、e、f,写的时候确实快,但一旦出错,回头查代码时会非常痛苦。代码是写给人看的,计算机不在乎变量名,但你自己在乎。
int total = price1 * count1 + price2 * count2 + price3 * count3;这一行是整个程序的核心。注意乘法运算符*的优先级高于加法+,所以这个表达式会先算三个乘积,再相加,不需要额外加括号。如果你不确定优先级,可以用括号明确一下,写上(price1 * count1) + (price2 * count2) + (price3 * count3)也完全没问题,代码只要能正确表达意图,美观是次要的。
最后cout << total << endl;输出结果并换行。endl是换行,在评测系统里通常要求输出后换行,所以别省。还有一个细节:理论上变量可以在用到的时候再定义,所以你也可以把int total = ...这句话放在最后写。C++对变量定义位置的要求不严格,但建议养成“用之前定义、定义后尽快使用”的习惯,减少变量作用域带来的麻烦。
3.2 Python 写法与简洁之道
如果你选Python,代码可以更短。先看最直接的版本:
price1, count1, price2, count2, price3, count3 = map(int, input().split()) total = price1 * count1 + price2 * count2 + price3 * count3 print(total)这种写法靠input().split()把一行输入按空格拆成多个字符串,再用map(int, ...)一次性转换成整数,最后用多个变量接住。这个“一行代码读入多个整数”的写法在Python里非常常用,建议熟练掌握。很多初学者会疑惑:为什么input()返回的是字符串,而map(int, ...)能直接转成整数?其实map相当于对每个元素执行一次int()转换,最后得到一组整数。
再给一个用循环的版本,更接近前面说的累加器思想:
total = 0 for _ in range(3): price, count = map(int, input().split()) total += price * count print(total)这里的total += price * count是total = total + price * count的简写,Python里这类复合赋值运算符很常见。_是循环变量的占位符,表示我们并不关心当前是第几次循环,只需要循环三次就够了。这个循环版本的泛化能力更强,如果题目改成“输入n行”,只要把range(3)改成range(n)就行。
Python的优点是语法灵活,但也要提醒一句:越灵活越容易写出“看起来对但提交就错”的代码。比如有人会写成price, count = input().split()而忘记转成整数,结果单价和数量都是字符串,字符串乘法在Python语法上是合法的(比如'3' * 2得到'33'),这种错特别隐蔽。所以初学者用Python,反而要更小心地确认变量类型。
3.3 样例验证与功能扩展
拿到代码以后,一定要用一个自己构造的样例去测试,不要只依赖题目给的样例。比如我习惯用这组数据:
3 2 2 5 1 3按我的理解,总价是3×2 + 2×5 + 1×3 = 19。把代码跑一遍,如果输出是19,说明主流程是对的。然后我还会再测一组极端数据,比如单价是1、数量是1,保证计算结果是1而不是别的怪数。组数少的情况下,这一步很快,但能帮你拦住大部分低级错误。
这道题还可以往两个方向扩展,用来练习变通能力。一个是加折扣:比如笔记本买5本以上打八折,这就需要在累加前用条件判断去修改单价或总价,考查的是分支结构。另一个是不知道具体几行数据:需要一直读到文件末尾,C++里用while (cin >> price >> count),Python里用while True加异常处理。这两个扩展都不难,但能帮你把一个知识点玩出花样,比死记模板强得多。
就以折扣为例,C++版本会写成这样:
if (count1 >= 5) { total += price1 * count1 * 0.8; } else { total += price1 * count1; }注意这里出现了小数,前面用int就不合适了,可能得用double,同时输出格式也要调整。这种“从一个基础题长出一个新考点”的练习方式,比反复做同类型题有效得多。
4. 新手最容易踩的坑与排查方法
4.1 变量相关:类型范围、初始化、命名
先讲一个我在批改代码时经常看到的错:把累加变量写成了int total;但不赋初值。在C++里,一个未初始化的局部变量里面存的是一个不确定值,如果后面直接用它做累加,结果完全不可预测。所以定义累加变量时,一定要顺手写上= 0。这个习惯在我见过的所有编程竞赛选手里都是标配,哪怕不是累加变量,任何变量在第一次使用前都应该明确知道它的值是什么。
另一个是数据范围的问题。这道题如果单价和数量都不大,int完全够用。但假如某道扩展题里单价能到10万、数量能到10万,单个乘积就是10的10次方,这时候int就装不下了,需要用long long。我的建议是:养成读题时顺手估算数据范围的习惯,别等到提交发现“答案错误”才反应过来。32位int的上限大约是21亿,任何一个计算过程中的中间值超过它,就要换更大的类型。
变量命名也很值得说。直接用a1、b1不是不能过,但一旦程序出错,你盯着a1和a2很难看出问题。我会用price1、count1这种语义化命名,虽然多敲几个字母,但调试时的幸福感提升是实打实的。尤其考试时要检查代码,一个好名字能让你快速定位到出错的那一行。
4.2 输入输出相关:格式、顺序、换行
输入输出是最容易被忽略、又最容易出问题的地方。第一个坑是读取顺序。如果题面先说“然后输入数量”,你就得按那个顺序来,不能自己想当然地先读单价。第二个坑是输出提示。初学者喜欢在cout里加一句 “答案是:”,这在本地跑没问题,但评测系统是按标准输出逐字符比对的,多一个字就算错误。所以竞赛代码里,输出必须严格遵守题面要求,不能加任何多余内容。
第三个坑是换行。有些题目要求输出后换行,有些没写你也要默认加换行。大多数评测系统对换行并不敏感,但养成cout << total << endl;的习惯可以减少意外。举个我踩过的例子:有次帮学生调代码,他用C++printf输出,格式串里漏了\n,样例过了,但提交到系统里因为多组数据连在一起而错误。从那以后我就特别强调,printf和cout的换行必须和题面一致,同样的道理也适用于Python的print,它默认带换行,所以不会有这个问题。
4.3 样例过了但提交报错:WA 排查思路
本地样例能过,提交上去却是“答案错误”(WA),这是初学者最困惑的时刻。我通常按顺序排查:先看输入是否完全按照题面读取,再看算法逻辑是否和算式一致,最后用极端数据测试。很多时候问题不在思路,而在一个不起眼的细节上。
对于这道题,我会重点检查乘法写没写错。真有学生把*写成+,或者把三个组的数据搞混。最直接的方法,是用紧密相关的两套数据分别测试。比如一组让三种文具单价数量全相同,另一组让某个数量为0,看输出是否符合手算结果。第一组能检验累加逻辑,第二组能检验乘法表达式里有没有遗漏项。
如果还找不到问题,我建议把代码打印出来,逐行在纸上模拟一遍运行,把每个变量的值标出来。这个方法虽然原始,但对初学者排查问题极其有效。我遇过不少案例,学生自己纸上跑一遍就发现问题了,根本不需要我来指点。说白了,会查错是比会写代码更重要的能力,这也是竞赛训练的一部分。记住一个原则:计算机一定是对的,出错的只可能是你对题面或语法的理解。
4.4 常见问题速查表
把这道题相关的坑整理成一张表,方便你对照检查:
| 错误症状 | 可能原因 | 解决方法 |
|---|---|---|
| 编译报错变量未定义 | 变量名拼写不一致 | 检查每个变量名是否完全相同 |
| 输出结果是一大串数字 | 字符串参与乘法 | Python里忘记int()转换 |
| 输出结果比预期大很多 | 乘法写成加法或变量复用 | 核对total的累加表达式 |
| 本地正常、提交全错 | 输出多了提示文字 | 删除所有多余输出 |
| 结果对但格式错 | 少换行或多了空格 | 检查endl和输出格式 |
| 数据一大就溢出 | 超出int范围 | 改用long long或更大类型 |
这张表不只适用于“买文具”这一道题,很多基础题的错误类型都是共通的。你可以在自己刷题时继续往里面补充,慢慢形成一份属于自己的避坑手册。遇到新错误就记录下来,放假前翻一翻,效果比考前临时抱佛脚好得多。
5. 从这道题出发:GESP 一级备考经验
5.1 刷题顺序与心态
一级备考最大的误区是贪难。有的学生刚学会循环,就跑去刷二级、三级的题,结果被虐得体无完肤,回头连基础题都不愿意做了。我给出的建议其实很朴素:先确保所有一级真题都能不看资料写出来,再往上走。像“买文具”这种题,你至少要能闭着眼在五分钟内写完并保证一次通过,才算真正过关。
刷题顺序上,我一般推荐按“输入输出计算 -> 条件判断 -> 循环 -> 综合应用”的顺序推进。每一步都把上一轮的基础动作练熟,再进入下一个知识点。写代码和健身有点像,动作不到位就加重,很容易受伤。这里说的受伤,就是自信心受挫。一个学生如果连续三次卡在同一类简单题上,很容易产生“我是不是不适合学编程”的错觉,其实只是基本功没跟上。
心态上,别小看简单题带来的正反馈。一道题一次通过,比做十道难题然后全错要有用得多。尤其对刚接触编程的同学,每一次“提交通过”都在强化一个观念:我是能把事情做对的人。这个观念会在后面支撑你跨过很多坎。考试的时候也一样,先把稳拿的题做对,心态稳了,再回头啃难题,成功率会高很多。
5.2 真题复盘的正确姿势
复盘比刷题更重要,这句话我反复给学生强调。做完一道题,至少问自己三个问题:我的思路和标准解法是否一致?有没有可以更简洁的写法?我在哪个环节犹豫了、卡住了?这三个问题,比单纯把代码抄一遍有用得多。
针对“买文具”这样的简单题,复盘的重点不是算法,而是基础动作有没有变形。比如输入输出是不是一遍写对,变量命名是不是清晰,有没有在细节上返工。如果一道简单题你花了二十分钟,那问题不在题,而在某个基础动作不够熟练,这时候就要针对那个动作做专项训练。我见过不少学生,题目是写对了,但中间反复删改了很多次,这种“侥幸过关”在考场上很难复制。
我常用的办法是给学生出三道同类型的变式题,比如把文具换成水果、把三种商品换成两种,逼他们在不同场景下用同一套逻辑快速解题。这个过程训练的不是记忆,而是把核心模式抽象出来的能力。刷题数量固然重要,但每一题都嚼透,效果会好得多。真题就那么多,你不把每一道嚼碎,刷十套试卷也可能只是原地踏步。
5.3 一级到二级的衔接建议
过了“买文具”这一关,接下来要面对的是二级考试里的分支嵌套、循环进阶和简单数组。这些内容听起来多,但你会发现,它们全是建立在一级基础之上的。变量用得不熟,二级的数组就容易越界;输入输出不熟练,二级的字符串处理就会手忙脚乱;连累加器都没形成肌肉记忆,二级的求和类题目会更痛苦。
所以我常说,一级的简单题不是终点,而是一个地基工程。你在“买文具”上付出的每一点细心,都会在未来某道更复杂的题上得到回报。比如当你学到数组时,你可能想起来:原来三种文具的单价和数量,用两个数组存起来再遍历求和,比写三个单独的变量更优雅。这个顿悟,恰恰是从这道一级题出发的。
备考节奏上,我个人建议二级不要急着报,先做几套二级真题感受一下难度,能稳定拿到及格分再考虑。毕竟考试是按级别递进的,跳级备考往往得不偿失。一级的知识点就像搭积木的底座,底座不稳,往上堆再多的积木也会塌。把“买文具”这类题吃透,后面的路会顺畅很多。
写到这里,我最后再说一点实操中的体会。这道“买文具”我会让每个学生至少写三遍:第一遍看着讲解写,第二遍自己独立写,第三遍过两天再写一遍。三遍下来,基础动作基本就固化住了。有人觉得重复没意义,但编程能力的提升,恰恰就是在这种看似枯燥的重复里发生的。如果你正在备考,不妨也试试这个方法,把最基础的题写到“条件反射”,考试时你自然会发现,紧张感少了一大半。