
1. 项目概述从“刷题”到“内功修炼”如果你正在学习C或C尤其是刚入门不久面对着一道道编程练习题是不是常常有这样的感觉题目看懂了样例也通过了但一提交就是各种“Wrong Answer”或者“Time Limit Exceeded”或者你看着别人简洁高效的代码再看看自己写的冗长且脆弱的逻辑总感觉中间隔着一层窗户纸。我当年学编程的时候这种感觉尤为强烈。后来我发现问题往往不在于“会不会写代码”而在于有没有真正理解题目背后的“考点”和语言本身的“特性”。“ZZNUOJ(C/C)基础练习1011——1020(详解版)”这个标题指向的正是一系列经典的编程入门练习题。ZZNUOJ很可能是一个在线判题系统Online Judge, OJ而1011到1020这十道题通常是设计用来巩固循环、条件判断、数组、基本算法等核心概念的。但“详解版”三个字才是关键——它意味着这不是简单的答案罗列而是对解题思路、代码细节、易错点和性能优化的深度剖析。在我看来刷这些基础题的目的绝不仅仅是为了“通过”。每一道题都是一个微型的工程项目它考察你如何将问题抽象成计算机能理解的逻辑如何选择合适的数据结构和控制流以及如何写出健壮、高效的代码。这个过程我称之为“内功修炼”。很多初学者急于求成直接去网上搜答案复制粘贴看到“Accepted”就心满意足。这其实是最大的误区。你错失的正是那个绞尽脑汁思考、调试、失败、再思考最终“顿悟”的成长过程。这份“详解”就是要带你走完这个完整的思考闭环让你知其然更知其所以然。接下来的内容我将假设你是一位有C/C基本语法基础比如知道变量、循环、分支的初学者带你逐一拆解这类基础练习题中常见的“坑”与“美”。我们会从最朴素的思路开始逐步优化并深入探讨一些看似简单却至关重要的编程习惯和思维模式。这十道题可能涵盖整数处理、字符判断、数列求和、简单数学问题等我们的目标是通过它们帮你搭建起解决更复杂问题的坚实脚手架。2. 解题核心心法理解题意与设计测试用例在动手写第一行代码之前有两件事比编码本身更重要彻底理解题意和设计全面的测试用例。这是区分“编程爱好者”和“严谨开发者”的第一道门槛。2.1 题目分析的“三步法”拿到一道OJ题我习惯用“三步法”来拆解第一步提取输入输出格式与约束。这是最机械但绝不能出错的一步。你需要像编译器一样精确地识别输入部分有多少个输入是整数、浮点数、字符串还是混合类型输入之间用什么分隔空格、换行、逗号题目是否说明输入包含多组数据这是OJ题的常见套路输出部分输出什么格式要求是什么比如末尾换行、保留小数点后几位、空格分隔常见的陷阱是“多一个空格”或“少一个换行”导致格式错误。约束条件数据范围是多少例如1 N 1000。这个范围直接决定了你选择的数据类型用int还是long long和算法复杂度是否可行。注意很多题目描述是“直到输入结束”或“输入包含多组测试数据”。在C/C中这通常意味着你要用while(scanf(“%d”, n) ! EOF)或while(cin n)这样的循环来读取。忽略这一点你的程序可能只处理了一组数据就结束导致错误。第二步抽象问题模型。抛开编程语言用自然语言或伪代码描述解决问题的步骤。例如一道题是“求数列1到N的和”模型就是“初始化总和为0从1循环到N每次将当前数加到总和上”。另一道题可能是“判断一个数是否为素数”模型就是“检查该数是否能被2到其平方根之间的任何整数整除”。这一步能帮你理清逻辑主干。第三步识别潜在“坑点”。这是经验之谈也是“详解”的价值所在。例如边界条件N0或N1时你的程序还能正确运行吗整数溢出求和时如果N很大用int类型存储总和可能会溢出需要用long long。浮点数精度涉及浮点数比较时直接使用可能因为精度问题出错通常要判断两数差的绝对值是否小于一个极小值如1e-9。循环条件与更新for循环的初始值、终止条件、步进值是否设置正确特别是处理数组时小心下标越界。2.2 测试用例设计的“黑白盒”思维不要依赖OJ系统给的几个样例。在本地你必须自己充当“严苛的判题机”。正常用例用题目给的样例验证基本功能。边界用例输入数据的上限和下限。例如题目说N0那就测试N1的情况题目说数组长度不超过1000那就测试长度为1000的情况。特殊用例0、负数如果允许、空输入、非常大的数。破坏性用例故意输入一些奇怪的、不符合格式的数据看看你的程序是否会崩溃或陷入死循环。我个人的习惯是在写完代码后会专门写一个test()函数或在main函数开头用几组硬编码的数据进行快速测试然后再去OJ提交。这能节省大量不必要的提交次数很多OJ平台有提交次数限制或惩罚。3. 经典题型详解与代码实现剖析下面我将选取几类在1011-1020题号区间内极具代表性的题型进行深度剖析。请注意具体的题目描述可能因OJ平台而异但解题思想和技巧是相通的。3.1 题型一数列求和与迭代计算这类题目的核心是寻找数列的规律并用循环实现。假设题目类似1011计算序列 2/1 3/2 5/3 8/5 13/8 ... 的前N项和。分子分母都是斐波那契数列。思路拆解观察规律分子、分母分别构成斐波那契数列。设分子为a分母为b。初始状态第一项是2/1所以可以初始化a2, b1。迭代关系下一项的分子是当前的ab分母是当前的a。即next_a a b; next_b a;。求和在循环中累加a / b到总和sum然后更新a, b为下一项的值。代码实现与陷阱#include stdio.h int main() { int N, i; double sum 0.0; long long a 2, b 1; // 使用long long防止后续计算溢出 long long temp; scanf(“%d”, N); for (i 0; i N; i) { sum (double)a / (double)b; // 注意类型转换否则整数除法会截断 temp a; a a b; b temp; } printf(“%.2f\n”, sum); // 假设要求保留两位小数 return 0; }实操心得类型转换是关键a和b是整数但求和需要小数结果。必须在除法前将其转换为double即(double)a / (double)b。写成a / b将执行整数除法得到错误结果。更新顺序的陷阱更新a和b时需要用一个临时变量temp保存旧的a值。如果直接写a a b; b a;那么第二句中的a已经是新值逻辑就错了。选择合适的数据类型虽然N可能不大但斐波那契数增长很快a和b用int可能很快溢出用long long更安全。这是一种防御性编程思维。3.2 题型二数字特征判断与数位拆解这类题目通常涉及对一个整数进行逐位操作比如求各位数字之和、判断水仙花数、数字反转等。假设题目类似1012给定一个三位整数判断其是否为水仙花数各位数字的立方和等于其本身。思路拆解分离出个位、十位、百位。计算立方和。与原数比较。代码实现与优化#include stdio.h int main() { int num, original, hundreds, tens, units; scanf(“%d”, num); original num; // 方法1利用整数除法和取余 units num % 10; // 个位 num / 10; // 去掉个位 tens num % 10; // 十位 num / 10; hundreds num; // 百位 if (original (hundreds*hundreds*hundreds tens*tens*tens units*units*units)) { printf(“YES\n”); } else { printf(“NO\n”); } return 0; }更通用的数位拆解循环适用于任意正整数int sum_of_cubes 0; int temp original; while (temp 0) { int digit temp % 10; sum_of_cubes digit * digit * digit; temp / 10; } // 然后比较 sum_of_cubes 和 original注意事项保存原始值在拆解数位的过程中变量num被不断修改。一定要用另一个变量如original保存原始值用于最后的比较。循环条件的思考通用循环while (temp 0)可以处理任意位数的正整数。当temp变成0时所有数位处理完毕。负数的处理如果题目可能输入负数需要先判断处理。通常的OJ题中这类数字特征题默认输入是非负整数。3.3 题型三简单模拟与过程再现这类题目要求你严格按照描述的规则一步步模拟一个过程。关键在于准确地将文字规则翻译成代码逻辑。假设题目类似1015猴子吃桃问题。猴子第一天摘下若干桃子当即吃了一半又多吃了一个以后每天早上都吃了前一天剩下的一半零一个。到第N天早上想再吃时发现只剩下一个桃子了。问第一天共摘了多少个桃子思路拆解这是典型的逆推问题。我们从第N天只剩1个反推回第一天。第N天桃子数 1第N-1天桃子数 (第N天桃子数 1) * 2通用公式peach_of_yesterday (peach_of_today 1) * 2代码实现#include stdio.h int main() { int N, i; int peaches 1; // 第N天的桃子数 scanf(“%d”, N); for (i N; i 1; i--) { // 注意循环从第N天倒推到第2天 peaches (peaches 1) * 2; } printf(“%d\n”, peaches); return 0; }实操心得循环方向与边界因为是从后往前推所以循环变量i从N开始大于1时继续因为我们知道第N天的值要推到第1天。循环次数是N-1次。如果写成从1到N-1的正向循环需要重新推导公式容易出错。逆推通常更直观。变量命名将变量命名为peaches而不是简单的x能让代码更易读尤其是在逻辑复杂的模拟题中。验证可以拿N4心算验证一下第4天1个第3天(11)*24个第2天(41)*210个第1天(101)*222个。逻辑是否正确一验便知。3.4 题型四格式化输出与图形打印这类题目不涉及复杂算法但极其考验对循环控制的理解和细心程度比如打印金字塔、菱形、数字矩阵等。假设题目类似1017打印一个由星号组成的空心数字梯形。思路拆解以打印一个简单的等腰三角形为例打印图形通常需要嵌套循环。外层循环控制行数内层循环控制每行打印的空格和星号。分析第i行从1开始前置空格数 总行数 - i星号数 2 * i - 1如果是“空心”三角形则只有第一行、最后一行和每行的第一个及最后一个位置打印星号其余打印空格。代码实现实心三角形#include stdio.h int main() { int n, i, j; scanf(“%d”, n); for (i 1; i n; i) { // 打印空格 for (j 1; j n - i; j) { printf(“ ”); } // 打印星号 for (j 1; j 2 * i - 1; j) { printf(“*”); } printf(“\n”); // 每行结束后换行 } return 0; }避坑技巧先画图再写公式在纸上画出小规模如n4的图形标出每行的空格和符号数量归纳出数学关系。这是最可靠的方法。注意换行符每行内容打印完毕后一定要打印一个\n换行。空心图形的判断条件内层循环中判断当前位置是否应该打印星号。通常是j 1 || j (2*i-1) || i n第一列、最后一列、最后一行。对于更复杂的图形判断条件也需要相应调整。使用在线工具或本地环境预览在提交前最好在本地运行将输出重定向到文件或用眼睛仔细检查格式确保空格和换行完全正确。4. 从“AC”到“优秀”代码优化与习惯养成一道题“Accept”之后工作并没有结束。审视自己的代码思考如何让它更“好”这个习惯能让你在未来走得更远。4.1 时间与空间复杂度初探对于基础题数据量小通常暴力解法也能过。但了解复杂度是进阶的必经之路。时间复杂度粗略估计你的代码执行基本操作的次数与输入规模N的关系。例如一个单层循环复杂度大概是O(N)嵌套的两层循环复杂度是O(N²)。在ZZNUOJ的基础题里N通常很小10^4O(N²)一般也能接受。但如果N达到10^5O(N²)的算法就很可能超时。空间复杂度你的程序使用了多少额外内存。例如如果你定义了一个大小为N的数组来存储数据空间复杂度就是O(N)。在不必要的情况下应尽量避免使用大数组。举例求素数。最朴素的方法是对于每个数n用2到n-1去除复杂度O(n²)。一点小优化是除到√n即可复杂度降到O(n√n)。再进一步可以使用“埃拉托斯特尼筛法”用O(n log log n)的复杂度一次性筛出大量素数。在基础练习中可能只需要第一种或第二种方法但知道有更优解能拓宽你的视野。4.2 代码风格与可读性清晰的代码是给未来的自己和他人的礼物。命名变量名sum、count、isPrime比s、c、flag好得多。缩进与空格一致的缩进通常是4个空格能让代码结构一目了然。运算符两边加空格如a b c;。函数化即使题目简单将独立的逻辑封装成函数也是好习惯。比如把“判断素数”写成一个bool isPrime(int n)函数主函数里调用它。这样主逻辑更清晰函数也便于复用和测试。注释在关键步骤、复杂的逻辑或易错点旁添加简短注释解释“为什么这么做”而不是“在做什么”。4.3 输入输出效率的考量在C中cin/cout默认与C的scanf/printf同步且cout在每次输出后刷新缓冲区在数据量极大时比如十万、百万级别可能会比scanf/printf慢。对于追求极限效率的竞赛有两个技巧在main函数开头加上ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);来解除cin/cout与stdio的同步并解除cin与cout的绑定可以大幅提升速度。直接使用C风格的scanf和printf。对于ZZNUOJ的基础练习数据量极小完全不需要考虑这个。但知道这个知识点当你在其他平台遇到“Time Limit Exceeded”时可以多一个排查方向。5. 常见“Wrong Answer”原因深度排查提交代码后看到“WA”Wrong Answer是最常见也最令人沮丧的情况之一。别慌系统性的排查能帮你快速定位问题。5.1 逻辑错误排查清单重新审题这是第一步也是最重要的一步。再读三遍题目描述确保没有误解任何细节。我无数次发现错误是因为把“不大于”看成了“小于”或者忽略了“多组数据输入”。检查边界条件输入为0、1、负数、最大值、最小值时你的程序行为是否正确循环的起始和结束值是否正确手动模拟用题目给的样例以及你自己设计的边界用例在纸上或用调试器一步步走一遍你的代码。关注每个变量的值变化是否如你所想。检查输出格式这是WA的一个高频原因。是否多打了空格是否少了换行浮点数输出是否按要求保留了小数位数例如要求%.2f你用了%f整数溢出这是另一个隐形杀手。计算中间结果或最终结果是否可能超过int的范围约±21亿如果可能果断使用long long。在C中long long的格式化输入输出是%lld在C中是cin 和cout 。浮点数比较这是精度问题的重灾区。永远不要用直接比较两个浮点数正确做法是判断它们差的绝对值是否小于一个很小的数称为epsilon。double a, b; // 错误if (a b) ... // 正确 #define EPS 1e-9 if (fabs(a - b) EPS) { // 认为a和b相等 }5.2 调试技巧与工具使用打印调试法printf debugging在怀疑的代码段前后插入printf语句打印关键变量的值。这是最朴素但最有效的方法之一。调试完后记得删除或注释掉这些调试语句。使用集成开发环境IDE的调试器如Visual Studio、VS Code配合C/C插件、CLion等。学会设置断点、单步执行、查看变量值、观察调用栈。这能让你直观地看到程序运行的每一步。构造极端测试数据如果WA了又找不到原因尝试构造一些小的、但可能触发特殊情况的随机数据与一个你认为正确的“暴力但肯定对”的程序或者手算的结果进行对比。这常常能帮你定位到出错的特定输入模式。5.3 “Presentation Error”与“Compilation Error”PE (Presentation Error)你的答案在数值上是正确的但输出格式不对。99%的情况是空格或换行符的问题。仔细对比题目要求的输出格式和你的输出一个字符一个字符地检查。CE (Compilation Error)编译错误。查看判题系统反馈的错误信息它通常会告诉你错误发生在哪一行。常见原因有拼写错误、缺少分号、头文件没写、使用了不支持的编译器扩展等。在本地确保能编译通过再提交。6. 学习路径建议与资源推荐刷完1011-1020只是一个开始。如何利用好这些基础练习构建你的编程知识体系6.1 建立你的“解题档案”不要刷完就扔。为每道你做过的题建立一个简单的笔记可以是一个Markdown文件也可以是一个笔记本。记录以下内容题目链接与描述方便回顾。你的核心思路用几句话概括。最终AC代码附上简洁、有良好注释的版本。遇到的坑与解决方法这是最宝贵的部分。比如“本题注意浮点数比较要使用EPS”“注意输入有多组数据要用while循环”。一题多解如果想到了更优的解法或者从别人那里学到了更优雅的代码记录下来并对比。相关知识点链接这道题主要考察了“循环”、“数组”、“素数判断”把这些关键词记下来。定期回顾这个档案你会发现很多问题具有共性你的解题能力会以网状结构增长而不是零散的点。6.2 循序渐进的OJ平台与题目集ZZNUOJ是一个很好的起点。当你觉得这些基础题游刃有余后可以尝试挑战更综合的题目。国内常见的OJ平台有杭州电子科技大学OJ (HDU OJ)题目量大且经典适合各个阶段的练习者。北京大学OJ (POJ)非常老牌题目难度梯度明显但界面较旧。洛谷 (Luogu)在国内非常受欢迎社区活跃题目有详细的分类和难度评级对新手友好有很多题解和讨论。Codeforces国际知名平台定期举办比赛题目质量高能极大锻炼思维和编码速度。建议从这些平台的“新手村”或“入门题单”开始按照“模拟-简单数学-简单算法排序、查找-简单数据结构数组、字符串-简单动态规划/搜索”的路径逐步深入。6.3 超越刷题项目与实践刷题是锻炼思维和语法熟练度的绝佳方式但它不是编程的全部。当你有了一定基础后比如能轻松解决ZZNUOJ上大部分基础题一定要开始做一些小项目来综合运用你的知识。命令行小工具比如一个简单的通讯录管理系统、一个文件内容统计工具、一个简单的计算器。参与开源在GitHub上找一些用C/C写的、标签为“good-first-issue”的小项目尝试修复一个bug或添加一个小功能。学习系统知识尝试用C语言深入理解计算机系统比如通过《C Primer Plus》学习更全面的语法通过《深入理解计算机系统》理解程序如何在计算机上运行。记住编程是一门实践的手艺。看懂和写出之间隔着无数次的调试和思考。ZZNUOJ的这十道题就像木匠学徒最初练习刨平、凿眼的十个基本动作。反复练习用心体会每一个细节你的“手艺”才会扎实。当你能清晰地解释每一行代码为什么这样写能预见到它可能在哪里出错并能优雅地解决它时你就真正掌握了这些基础并为学习更复杂、更强大的技术打下了坚不可摧的基石。