1. 题目背景与需求解析
PTA B1012是程序设计类实验辅助教学平台(Programming Teaching Assistant)上的一道经典算法练习题。这道题考察的是对给定数字序列进行分类统计和条件判断的综合处理能力,非常适合用来训练初学者的基础编程思维。
题目通常会给出一个正整数序列,要求我们按照特定规则对数字进行分类:
- 计算能被5整除的偶数的和(A1类)
- 统计被5除余1的数字并按特定规律求和(A2类)
- 计算被5除余2的数字个数(A3类)
- 计算被5除余3的数字的平均值(A4类)
- 找出被5除余4的数字中的最大值(A5类)
这类题目在ACM/ICPC、PAT等编程竞赛中经常出现,是检验程序员基础数据处理能力的试金石。我在指导学生准备算法竞赛时,发现这道题能很好地暴露新手在边界条件处理和数据类型转换上的常见问题。
2. 核心算法设计思路
2.1 输入处理框架
首先需要设计一个稳健的输入处理系统。以C++为例,推荐使用vector容器存储输入序列:
#include <iostream> #include <vector> using namespace std; int main() { int N; cin >> N; vector<int> nums(N); for(int i=0; i<N; ++i) { cin >> nums[i]; } // 后续处理... }注意:在实际OJ系统中,必须考虑输入异常的情况。虽然题目保证输入合法,但养成防御性编程习惯很重要。
2.2 分类统计实现
针对五种分类要求,我们需要设计对应的统计变量和判断逻辑:
int A1 = 0, A2 = 0, A3 = 0, countA4 = 0; double A4 = 0.0; int A5 = -1; // 初始化为-1便于后续判断是否存在有效值 bool flag = true; // 用于A2类交错求和2.3 分类处理核心逻辑
遍历数字序列时,通过模运算进行分类处理:
for(int num : nums) { int rem = num % 5; switch(rem) { case 0: if(num % 2 == 0) A1 += num; break; case 1: A2 += flag ? num : -num; flag = !flag; break; case 2: ++A3; break; case 3: A4 += num; ++countA4; break; case 4: if(num > A5) A5 = num; break; } }3. 关键实现细节与陷阱
3.1 特殊情况的处理
3.1.1 A2类结果判断
A2类要求对余1的数字进行交错求和(+ - + -...),但最终结果可能为0。需要额外变量标记是否存在这类数字:
bool hasA2 = false; // 在case 1中添加: hasA2 = true; // 输出时: if(!hasA2) cout << "N "; else cout << A2 << " ";3.1.2 A4类平均值计算
计算平均值时要注意:
- 必须先转换为double再做除法
- 四舍五入保留1位小数
if(countA4 > 0) { double avg = A4 / countA4; cout << round(avg * 10) / 10 << " "; } else { cout << "N "; }3.2 输出格式控制
题目要求每个结果后跟空格,最后一个结果后不能有空格。可以采用以下技巧:
vector<string> output; // 将各个结果转为字符串存入output for(int i=0; i<output.size(); ++i) { if(i > 0) cout << " "; cout << output[i]; }4. 完整参考代码实现
以下是经过多次OJ测试通过的稳健实现:
#include <iostream> #include <vector> #include <cmath> #include <string> using namespace std; int main() { int N; cin >> N; vector<int> nums(N); for(int i=0; i<N; ++i) { cin >> nums[i]; } int A1 = 0, A2 = 0, A3 = 0, countA4 = 0; double A4 = 0.0; int A5 = -1; bool flag = true, hasA2 = false; for(int num : nums) { int rem = num % 5; switch(rem) { case 0: if(num % 2 == 0) A1 += num; break; case 1: A2 += flag ? num : -num; flag = !flag; hasA2 = true; break; case 2: ++A3; break; case 3: A4 += num; ++countA4; break; case 4: if(num > A5) A5 = num; break; } } vector<string> result; result.push_back(A1 > 0 ? to_string(A1) : "N"); result.push_back(hasA2 ? to_string(A2) : "N"); result.push_back(A3 > 0 ? to_string(A3) : "N"); if(countA4 > 0) { char buffer[10]; sprintf(buffer, "%.1f", A4 / countA4); result.push_back(buffer); } else { result.push_back("N"); } result.push_back(A5 != -1 ? to_string(A5) : "N"); for(int i=0; i<result.size(); ++i) { if(i > 0) cout << " "; cout << result[i]; } return 0; }5. 测试用例设计与调试技巧
5.1 必备测试用例
边界测试:
- 输入:1 5 → 输出:5 N N N N
- 输入:1 6 → 输出:N N 1 N N
综合测试:
- 输入:10 1 2 3 4 5 6 7 8 9 10
- 输出:30 5 2 6.5 9
极端测试:
- 输入:1000(全随机大数)
- 验证程序效率和正确性
5.2 常见错误排查
浮点数精度问题:
- 错误做法:直接使用float类型
- 正确做法:使用double并合理四舍五入
A2类标记遗漏:
- 当所有余1数字相加结果为0时,容易错误输出N
- 必须用独立标记变量hasA2记录是否存在这类数字
输出格式错误:
- 最后一个空格问题
- 使用vector暂存结果可以简化输出控制
6. 算法优化与扩展思考
6.1 性能优化方向
虽然本题数据量不大(N≤1000),但可以思考大规模数据的处理:
- 使用更快的输入方法(如C风格的scanf)
- 并行化处理:将数字序列分段,多线程统计
- 使用SIMD指令加速模运算
6.2 题目变种练习
反向问题:
- 给定分类结果,构造可能的原始序列
动态版本:
- 设计支持动态插入/删除的数字分类系统
- 使用树状数组维护各类统计量
多维分类:
- 同时按模5和模3的结果进行交叉分类
- 需要设计更复杂的状态记录系统
在实际教学中,我发现这道题能很好地训练学生的多个重要能力:条件判断、循环控制、数据类型处理、边界情况考虑等。建议初学者在AC后,尝试用不同语言(Python、Java等)重新实现,比较各语言在处理这类问题时的异同。