1. 这道题到底在考什么
先说结论:PTA B1011 “A+B 和 C”是PAT(程序设计实验辅助教学平台)乙级题目的第一道“判断类”题目,它的核心考点非常直白:给定三个整数A、B、C,判断A+B是否大于C。
很多刚接触在线评测系统(OJ)的同学,第一眼看到这个题目会觉得“这也太简单了吧”,实际上手之后才发现,卡住的点一个接一个:数组越界、整数溢出、输出格式不对、测试点过不了。这道题表面上是考“加法”,实际上是考三类基本功:
- 能否正确理解多组测试数据的输入方式;
- 能否正确选择变量类型,避免整数溢出;
- 能否严格按照输出样例的格式进行输出。
这三件事,几乎就是PAT乙级前20题的全部核心。换句话说,把这道题吃透了,后面做题的底子就打好了一半。
这个题目的典型应用场景分为三类:
- 初学者第一次接触OJ判题系统,用来熟悉“提交-编译-评判”的完整流程;
- 参加C/C++、Java、Python课程的实验作业,作为课堂练习;
- 备考计算机考研、复试上机或程序设计竞赛的入门热身。
不管你是刚学编程一个月的新手,还是准备考研机试的二战选手,这道题都值得认真对待。下面从题目本义出发,逐步拆解解题思路、代码写法、踩坑记录和排查技巧。
2. 题意拆解与整体设计思路
2.1 题目到底在问什么
原题描述非常简短:给定区间内的三个整数A、B、C,请判断A+B是否大于C。
因为原题要求“输入第1行给出正整数T(测试用例的个数),随后给出T组测试用例,每组占一行,顺序给出A、B、C”,所以这道题天然地带有“多组数据”的处理需求。它的结构本质是:
- 先读一个整数T;
- 循环T次;
- 每次读A、B、C三个整数;
- 判断 A + B > C 是否成立;
- 按固定格式输出,格式是 “Case #x: true / false”,其中x是组号(从1开始)。
注意这里的“给定区间”四个字,就是最不能忽略的细节。题目里给出的范围是-2^31 到 2^31-1,也就是C语言中int类型的取值范围。这就是一个明摆着的陷阱:如果直接把A和B用int存,A+B在计算过程中就可能会溢出。这个问题是经典考点,下面单开一节讲。
2.2 解题思路的两种方向
这道题在思路上没有太多弯弯绕,判断逻辑本身一句话能说完。真正需要设计的是“怎么存储A、B、C,以及怎么处理加法溢出”。
方向一:用更大范围的类型装下加法结果。C语言用long long(C++同样支持),Java用long,Python直接不用管(动态整型不溢出)。这几乎是最省心的做法,也是大多数人采用的办法。原因是题目给出的区间是int的范围,两个int最大值相加,结果是-2^32到2^32-2,long long(-9.22e18到9.22e18)完全兜得住。
方向二:用int存储,但把比较方式变形成减法,即判断 A > C - B。这种做法的核心是避开A+B的加法本身,用“C - B”替代加法。C和B都落在int范围内,C - B的结果落在-2^32+1到2^32-1之间,不会超出int范围。所以这种方法也是合法的。
实际刷题中,我个人更建议方向一。原因有两点:
- 代码可读性好,别人一看就懂;
- 方向二需要在减法基础上再做一次关系运算,容易让人绕晕:写成 A > C - B 时,脑子里必须清楚这是“A + B > C”的等价变形。如果对移项不敏感,反而容易判错边界。
2.3 输出格式的“隐性得分点”
这道题还有一层考验:输出格式必须严格匹配。题目要求输出格式是:
Case #1: true Case #2: false具体来说,每组结果占一行,Case后面有一个空格,#号后面紧跟组号,组号后跟冒号,冒号后面有一个空格,然后是小写字母开头的true或false。踩过坑的人都知道:把true写成True、把数字前多加一个空格、在行尾多打一个回车,都不至于判错,但是组号从0开始数就会导致格式错误或答案错误。
这里有一个最重要的“隐性规则”必须记住:OJ判题系统对空白字符的容忍度通常是行末多余空格会被忽略,但是行内多了一个空格或少了空格,一律判格式错误(PE)或答案错误(WA)。所以输出时尽量用printf或格式化函数一次性拼好,不要手写一堆拼接字符串。
3. 核心细节解析与数据类型的选择
3.1 为什么int在这里是个坑
题目中说“给定区间内的三个整数A、B和C”,这个区间是[-2^31, 2^31-1]。在C/C++的常见32位系统里,int的范围恰好就是[-2^31, 2^31-1]。这意味着:
- 如果A取2^31-1,B取2^31-1,那么A+B就是2^32-2,远超int能表示的范围;
- 这种情况就是“有符号整数溢出”,在C/C++中是未定义行为(UB,Undefined Behavior)。实际操作中,它会让结果变回负数或产生一个看似“随机”的值,直接把判断搞错。
我用一个最简单的例子说明:假设A=2147483647(int最大值),B=1。如果用int保存A和B,然后直接写if (a + b > c),那么a+b在多数机器上会变成-2147483648(因为二进制补码回绕),判断结果直接反向。你以为是true,输出却是false。
这就是这道题最大的坑,也是它作为“基础题”却拥有高错误率的核心原因。解决办法就是:编译器里用long long,Java里用long,Python用户则完全不用操心。
也有人会问:那如果我用C语言,但系统里int是64位的呢?这种想法很危险。你写代码时永远不要假设int是多少位,宁可全部用long long,也不要赌环境。在OJ的GCC/G++环境下,int通常就是32位,不会为这种题目放宽。所以,用long long是稳定且可移植的写法。
3.2 三种语言的类型选择对比
| 语言 | 推荐类型 | 理由 | 备注 |
|---|---|---|---|
| C | long long | 保证64位,容纳两个int之和 | 用%lld读取和输出 |
| C++ | long long | 同样理由,兼容C写法 | cin/cout也可,但printf更顺手 |
| Java | long | long是64位,足够 | 注意类的命名必须是Main |
| Python | 整型int | Python int是任意精度的,自动扩容 | 无溢出顾虑,但也要注意输入解析 |
请记住,这道题考察的重点就是“类型”,如果你在提交后出现“答案错误”,先检查变量类型,大概率就能定位问题。
另外提一点,如果是GCC环境,scanf读取long long必须用%lld,用%d读会截断数据,导致莫名其妙的结果。新手非常容易在scanf这里栽跟头,我见过太多提交失败都是因为写成scanf("%d %d %d", &a, &b, &c),而a、b、c是long long类型——这种错误编译器不会报错,但运行结果一定有问题。
3.3 关于“给定区间”是否需要特判
有些同学会问:既然题目给了区间,那我是不是要自己写越界检查?比如判断输入是否合法?答案是:不要这么做。在线评测系统的测试数据一定在题目给定范围内,你只需要按照正常逻辑处理,不需要考虑输入非法的情况。写越界检查反而可能把本来能通过的正确代码改错。
这点适用于几乎所有OJ题。除非题目明确说明“输入可能包含非法数据,需要输出错误提示”,否则一律按输入合法处理。
4. 代码实现与完整实操过程
4.1 C语言参考实现
直接上一个最稳妥的C语言写法:
#include <stdio.h> int main() { int T; long long A, B, C; scanf("%d", &T); for (int i = 1; i <= T; i++) { scanf("%lld %lld %lld", &A, &B, &C); if (A + B > C) { printf("Case #%d: true\n", i); } else { printf("Case #%d: false\n", i); } } return 0; }把这个代码逐行拆开看,你会发现每一个步骤都有讲究:
long long A, B, C;是关键,直接避免加法溢出;scanf("%lld", &A)必须对应long long,不能是%d;- for循环的计数器i从1开始,因为输出要求组号从1开始;
- 变量名大小写:题目里是A、B、C,代码里保持一致,方便检查;
- if/else直接输出true或false,格式是完全固定的。
运行样例时,输入:
4 1 2 3 2 3 4 2147483647 0 2147483646 0 -2147483648 -2147483647输出应该是:
Case #1: false Case #2: true Case #3: true Case #4: true第3组是“A取int最大值,B取0,C取int最大值-1”,A+B明显大于C;如果这里用int算加法,A+0并不会溢出,但第4组里,A=0、B=-2147483648、C=-2147483647,B比C小1,A+B=-2147483648,不大于-2147483647,所以输出为false。这个用例可以帮你测试负数场景。
4.2 C++实现与iostream的注意点
C++的写法有两种主流风格:
风格一:保留scanf/printf
#include <cstdio> int main() { int T; long long A, B, C; scanf("%d", &T); for (int i = 1; i <= T; i++) { scanf("%lld %lld %lld", &A, &B, &C); printf("Case #%d: %s\n", i, (A + B > C) ? "true" : "false"); } return 0; }风格二:用cin/cout
#include <iostream> using namespace std; int main() { int T; long long A, B, C; cin >> T; for (int i = 1; i <= T; i++) { cin >> A >> B >> C; cout << "Case #" << i << ": " << (A + B > C ? "true" : "false") << endl; } return 0; }两种都能过。唯一要提醒的是:如果大量使用cin/cout,记得加上ios::sync_with_stdio(false); 和 cin.tie(nullptr);,否则在T比较大的时候有可能超时。这道题T本身不大,但养成这个习惯对后续刷题很有好处。
4.3 Java实现与类名陷阱
Java版本最大的坑是类名必须是Main,否则在OJ上编译报错或无法运行。提交时,确保文件里的public class叫做Main,而不是B1011、AplusB或其他自定义名字。
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int T = sc.nextInt(); for (int i = 1; i <= T; i++) { long A = sc.nextLong(); long B = sc.nextLong(); long C = sc.nextLong(); if (A + B > C) { System.out.println("Case #" + i + ": true"); } else { System.out.println("Case #" + i + ": false"); } } sc.close(); } }Java中long的范围是-9.22e18到9.22e18,所以int范围内的两个数相加完全没问题。如果你的机器上Java的int也是32位,直接用int会在同样边界场景下爆炸。所以请老老实实写long。
4.4 Python实现的简洁写法
Python是最简单的,甚至不需要考虑数据类型问题:
T = int(input()) for i in range(1, T + 1): A, B, C = map(int, input().split()) if A + B > C: print(f"Case #{i}: true") else: print(f"Case #{i}: false")要注意的点有两个:
- input().split()默认按空格分割,如果同行数据之间有多个空格也能正常切分;
- f-string里的
#没有特殊含义,不需要转义。
4.5 本地自测与提交流程
写好代码后,不要急着提交,先在本地运行一次样例。任何一个OJ题目,你都可以按这个流程操作:
创建源文件,如
b1011.c;编译:
gcc b1011.c -o b1011,C++就用g++ b1011.cpp -o b1011;运行:
./b1011,然后手动输入样例数据;比对输出结果与题目给出的样例输出,一字不差才算通过;
再额外测试一组边界数据:
- A = 2147483647,B = 2147483647,C = -2147483648,应该输出true;
- A = 0,B = 0,C = 0,应该输出false;
- A = -2147483648,B = -2147483648,C = -2147483647,应该输出false。
确认无误后再提交。
这段流程看着简单,但很多人喜欢“写完直接交”,结果反复因为同样的错误浪费提交次数。OJ判题系统是不会告诉你“你的代码哪里错了”的,你只能靠本地测试和逻辑推理来定位问题。所以本地自测是刷题的基本功。
5. 常见错误与排查技巧实录
5.1 最容易犯的三个错误
根据我的观察和实际经验,这道题最常见的错误集中在三个点:
第一个错误:scanf格式符用错。把long long类型读成%d,导致数据截断,结果完全不可控。排查方法很简单:编译时开启警告gcc -Wall,如果出现format相关的warning,就说明格式符和参数类型不匹配。如果已经提交了,先打印A、B、C的输入值,逐组检查。
第二个错误:组号从0开始。很多第一次写循环的人会写for(int i = 0; i < T; i++),然后printf里直接输出i,导致第一组输出Case #0。题目明确要求组号从1开始,这就是答案错误了。排查方法:对比样例输出,第一行应该是Case #1。
第三个错误:输出true/false的拼写或大小写问题。题目要求小写字母true和false,写成True、TRUE、False等一律判错。没有别的技巧,只能看清楚示例输出。
5.2 边界值专项测试
我在本地测这道题时,固定会用这样一组边界值:
| A | B | C | A+B是否>C | 期望输出 |
|---|---|---|---|---|
| 2147483647 | 0 | 2147483646 | true | true |
| 2147483647 | 2147483647 | -2147483648 | true(但int下极小可能false) | true |
| -2147483648 | 0 | -2147483647 | false | false |
| -2147483648 | -2147483648 | -2147483647 | false | false |
| 0 | 0 | 0 | false | false |
| 1 | 2 | 3 | false | false |
第二行就是专门用来测溢出的用例。如果你用int存储A和B并且直接相加,结果往往是一个负数或随机值,判断就会出错。本地测一遍这组数据,基本就能确认代码是否安全。
5.3 运行时错误(RE)的排查
有时候提交后不是“答案错误”,而是“运行时错误”或“段错误”。这道题里出现RE的原因一般有三种:
- 数组越界:如果你用了数组先存储所有输入结果,然后一次性输出,数组大小没开到T足够大,就会越界。解决方法是直接边读边输出,不要存数组;
- 变量未初始化:某些写法里先声明变量再用scanf读,由于scanf失败或格式错,变量保留未定义值,后续判断出错;
- 常见的内存问题:如果代码里用了指针或动态分配,请检查释放和边界。
就这道题而言,最简单稳妥的方案就是上面给出的边读边输出模式,既不需要数组,也不需要缓冲,天然避开RE。
5.4 超时问题的排查
这道题基本不可能超时,但如果你用的是Java的Scanner,且T特别大,输入解析会成为瓶颈。排查方法:
- 如果你是用C语言,并且用了getchar()逐字符解析数字,那么理论上不会超时;
- 如果你用C++但又开了cin/cout且没关同步,T上万的时候有可能超时;
- 如果你用Java,建议换BufferedReader或使用Scanner但不要做多余操作。
PAT这类平台的超时时间通常给得相对宽松,这道题的T一般不超过10,正常写法都过。但养成“关流同步”的习惯没有坏处。
5.5 一个隐藏的格式细节
有些同学纠结:输出时行尾多了一个空格,会不会被判定为对?不同OJ处理方式不一样。PAT采用的判题逻辑对行尾空格通常不敏感,但我不敢打包票所有OJ都一样。稳妥的做法是:不要在任何输出行末尾主动添加多余空格。这道题的输出格式是固定字符串,不大容易犯错,但如果你自定义输出,在printf的格式串里一定要严格控制空格的位置。
比如,在写printf("Case #%d: true\n", i);时,冒号后面那个空格就是标准格式的一部分,不能省。省了以后,输出会变成Case #1:true,和样例对不上,必然判错。
5.6 关于在线IDE和本地编辑器的差异
很多人喜欢在网页版的在线IDE里直接调试。在线IDE方便是方便,但环境和本地编译器可能不一致,导致同样的代码在本地通过、在OJ上却报错。遇到这种情况,优先检查编译器版本和标准:
- C语言请用C99或更高标准,long long在这个标准下是内置类型;
- C++请用C++11或更高;
- Java版本一般不会太老,避免使用过于新潮的语法(如var)就没问题。
如果你在本地用Visual Studio调试,注意VS的C编译器对scanf系列函数会提示“_CRT_SECURE_NO_WARNINGS”。这不是代码错误,但在提交到Linux平台的OJ时要保证代码本身与MSVC扩展无关。最稳妥的办法是直接用gcc/g++作为本地编译器,让本地环境和OJ保持一致。
6. 从这道题延伸出去的经验与刷题建议
6.1 一道题引申出的三个关键习惯
这道题虽然只有20行代码左右,但能把三类习惯练出来:
一是“读题先读范围”。不夸张地说,PAT乙级里一半的坑都藏在范围描述里。B1011告诉你int的范围,就是暗示你用long long;B10xx里有很多类似的情况,题目说“不会超过2^31”或者“保证在int范围内”,你就得判断自己需不需要换类型。
二是“提交前先测边界”。测试数据不能只用题目样例,因为样例通常很温和,不会触发溢出。养成自我构造边界用例的习惯,能帮你提早暴露问题。
三是“输出格式必须逐字符比对”。很多人在改代码时只关注逻辑,忽略了格式。实际上,OJ的判分设计里格式错误也是一类明确的结果。养成比对样例输出的习惯,能减少大量无效提交。
6.2 我实际刷这道题时的体会
说实话,第一次写这道题时,我也没注意区间的问题。当时用的C语言,直接定义了四个int,代码逻辑和样例都通过,但提交上去有两个测试点一直过不了。后来我打印出中间结果,发现出现了一个很大的负数,这才意识到是加法溢出了。
把int改成long long之后,瞬间全绿。这个经历对我后来的刷题帮助很大,因为它让我养成了一个条件反射:以后只要看到题目里出现整数范围,第一反应就是确认加法、乘法是否会超出当前类型范围。
还有一个细节我想多说一句:千万不要觉得这道题“太简单”就跳过。基础题的训练价值不在于“会不会做”,而在于“能不能一遍做对”。如果你每次做简单题都能保证不用调试、提交即通过,那考试和比赛时的容错率会高很多。
6.3 后续可以继续挑战的同类题目
做完B1011之后,可以按顺序往后刷。这类基础题中,有几道和B1011难度接近但手法略有不同:
- 读入多个整数并计算某种统计量;
- 按固定格式输出多组结果;
- 涉及溢出或类型选择的题目。
这些题的核心套路都包含在B1011里:正确的输入解析、合适的类型选择、严格的输出格式、边界测试。把这一套流程走顺了,你后面解题的稳定性会明显提升。
我个人建议每次刷题都建立一个本地测试文件,把样例、边界用例、随机数据都记下来。遇到新题先自己测一遍再提交,长期坚持下来,你的准确率会比直接交题的同行高很多。
最后再说一句:编程题不是看会的,是写会的。哪怕你觉得这道题简单到不行,也请亲手把它从新建文件到提交全过程完整走一遍。这一步走完,你才算真正吃透了这道题。