简介:一份覆盖西北工业大学在线编程比赛(NOJ)116道真题及解答的Word文档,面向备战编程竞赛、复习算法与数据结构、以及提升C/C++代码能力的读者。题目按难度与考点编排,包含基础算法、数学问题、字符串处理、链表操作、排序、查找、大数运算等类型,例如边沿与内芯的差、操场训练、插入排序、创建与遍历职工链表、大数乘法与除法等,每题附有可复制代码和简要题目说明,适合按需查阅或集中刷题。文档由单个doc文件组成,压缩包大小303KB,轻量易用。目前已有231人学习下载,是西工大NOJ练习者常用的参考答案来源。需要留意的是,Word版代码可能存在缩进或符号格式异常,复制后请适当调整;整体而言,这份资料既能为初学者提供完整的训练题集,也能帮助竞赛选手快速核对思路,具有较强的实践参考价值。
1. 西工大 NOJ 这份“116 题及答案 word 版”到底是什么,值不值得找
第一次听说“西工大 NOJ”的人,多半是在开学第三周被 C 语言作业逼的。NOJ 是学校里的在线判题系统,全称不难猜,就是 Network Online Judge,西工大自己的版本。它把平时作业、上机考试、期末模拟全塞进同一个网页里,你写好代码提交,系统编译运行后立刻告诉你“通过”还是“答案错误”,多一个字少一个空格都不行。很多人找的那份 116 题及答案 word 版,就是把常见题目和参考解答整理成电子文档流传下来的校内资料,覆盖从“输入输出”到“结构体、指针、递归”的一整条学习线。它解决的不是“我不会写代码”,而是“我不知道这题系统到底在测什么、边界条件卡在哪、为什么本地跑得好好的提交就判错”。
这东西适合三类人:刚开始接触 OJ、被编译错误和格式错误反复摩擦的新手;期中期末前想快速过一遍题型、查漏补缺的赶路人;以及想搞明白“为什么网上抄来的答案能过、我自己改一版就超时”的钻研党。说白了,它是一份按题型归好类的真题集,不是某个高深项目的源码,价值全在“题怎么解”和“坑在哪”这两件事上。我当年把 116 题按类型拆开刷,前后花了大概两周,最大的感受是:真正难的从来不是语法,而是把题目描述翻译成边界条件和算法选择。这份文档能帮你缩短“看懂题”到“写对题”之间的距离,但它不会替你思考——如果你想靠它投机,后面有得是坑。
2. 拆解题型:116 题到底在考什么,按什么顺序刷最省力
2.1 输入输出与格式控制:第一道坎比想象中高
NOJ 的前十几道题几乎全是“读两个数,算和/差/积/商”这种级别,但挂科率一点不低。原因不是你不会算,而是输出格式。比如题目要求“输出结果占一行,行末无多余空格”,你多打一个空格,系统就给你判 Presentation Error;要求“结果保留两位小数”你写成%f,直接 Wrong Answer。系统评测时拿你的输出和标准输出逐字节比对,哪怕末尾多一个换行都可能报错。所以刷这部分题,练的不是语法,是“读懂题目输出要求”的能力。
我当时给自己定的规矩是:先看输入描述,再看输出描述,最后才看样例。很多同学习惯先复制样例跑通就算了,结果题目里写着“多组输入,每组占一行”,你只处理了一组数据,自然怎么提交都不对。多组输入的标准写法也很固定:
#include <stdio.h> int main() { int a, b; // 注意 while 循环配合 EOF 判断,这是多组输入最常见的形态 while (scanf("%d %d", &a, &b) != EOF) { printf("%d\n", a + b); } return 0; }这段代码的逻辑核心是scanf的返回值:成功读入两个整数返回 2,读到一个返回 1,读到文件结尾返回 EOF。OJ 测试时会把所有测试数据当作一个文件喂给程序,循环直到读完,才会覆盖到所有用例。如果你只在 main 里写一次scanf加一次printf,那遇到多组数据必然翻车。另一个容易忽略的点是return 0;,有的编译器不写也能过,但 NOJ 的编译环境通常默认 C99 或更严格的标准,建议每段代码都带上,免得出幺蛾子。
2.2 分支与循环:边界条件的第一次爆发
刷到中间部分,题目开始混合 if、switch、for、while,难度直线上升。这个阶段的问题不在语法,而在“你没想到的特殊情况”。比如判断闰年,条件不是“能被 4 整除”,而是“能被 4 整除但不能被 100 整除,或者能被 400 整除”。再比如求最大公约数,用穷举法从大往小找也写得出来,但 n 一大就跑不动了,这时候才有人回头学辗转相除法。NOJ 的测试数据会专门卡这些边界,你漏一个条件就错一组,偏偏系统不告诉你错在哪组,只给你一个红红的 Wrong Answer。
我一般会建议新手在这个阶段养成“先写边界再写主逻辑”的习惯。所谓边界,就是输入的最小值、最大值、零、负数、空串、单个字符这些极端情况。判断完边界再走正常逻辑,代码虽然多几行,但能挡住大部分隐藏用例。
#include <stdio.h> int main() { int n; scanf("%d", &n); // 先处理边界:n <= 0 时直接返回,避免后续除零或数组越界 if (n <= 0) { printf("0\n"); return 0; } int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } printf("%d\n", sum); return 0; }这里if (n <= 0)就是典型的边界保护。虽然题目可能说 n 是正整数,但评测数据偶尔会夹带私货,多写这一层判断没有任何坏处。循环变量i从 1 到n,注意别写成i < n,那会少加最后一项。这类错在 NOJ 里属于高频低级失误,几乎每个刷题的人都会犯一次。
2.3 数组与字符串:下标越界是头号杀手
数组和字符串是 116 题里占比最大的一块,也是新手从“会写”到“写对”的分水岭。C 语言数组从 0 开始计数,长度为 n 的数组,合法下标是 0 到 n-1。写循环时for (i = 0; i < n; i++),大部分人知道,但一遇到“下标从 1 开始计数”的题目描述就懵了,要么多开一位数组,要么强行i-1,反而容易错。我的习惯是统一按 0 开始写,如果题目要求从 1 开始,那就声明int arr[n + 1],把 arr[0] 空着不用,代码可读性和安全性都更好。
字符串相关的题更麻烦,因为char数组的结尾要手动补'\0',否则strlen和strcmp会越界读到野内存。有个经典场景是读一串字符统计大写字母个数,你定义char s[100],输入 99 个字符还能撑住,输入 100 个直接溢出去,后面变量的值都被冲掉,排查半小时都找不出原因。安全做法是定义足够大的数组,比如题目没给长度上限就开char s[1024],然后用scanf("%s", s)或gets读入。如果题目明确说“最长 100 个字符”,数组至少开 101,多留一个位置放结束符。
2.4 函数与递归:理解调用栈才能不晕
递归题是 116 题里劝退率最高的一批,比如汉诺塔、斐波那契、全排列。很多同学把递归理解成“函数调用自己”,但真正写的时候无从下手,因为脑子里没有“调用栈”这个概念。每次递归调用都会在栈上压一层新帧,参数和局部变量各有独立副本,递归终止条件就是“栈开始回收”的起点。写递归第一件事不是写调用,而是写终止条件,没有终止条件的递归就是死循环,很快把栈爆掉报 Segmentation Fault。
斐波那契是典型例子,如果你用朴素递归写fib(40),计算量是恐怖的指数级,运行时间直接超限;但如果你加一个memo数组做记忆化,每次算完存起来,下次直接用,复杂度立刻降到线性。这个思路叫动态规划,但 116 题里不会直接点名考 DP,而是让你在“超时”中自己去领悟。这也是 NOJ 这类平台最好的地方:它不是教你知识点,而是让你在评测的红叉里学会优化。
#include <stdio.h> long long memo[100] = {0}; long long fib(int n) { // 终止条件:前两项直接返回 if (n == 0 || n == 1) { return n; } // 记忆化:已经算过就直接取,避免重复递归 if (memo[n] != 0) { return memo[n]; } memo[n] = fib(n - 1) + fib(n - 2); return memo[n]; } int main() { int n; scanf("%d", &n); printf("%lld\n", fib(n)); return 0; }这段代码里memo是核心。第一次调fib(5)时,它会递归算出 fib(4) 和 fib(3),但 fib(4) 又会算 fib(3) 和 fib(2),如果没有 memo,fib(3) 会被重复算好几遍;有了 memo,第二次遇到 fib(3) 直接返回存好的值,整个计算时间从指数级降到线性。这也解释了为什么同样的思路,有人过了有人超时,差的不是智商,而是“有没有把算过的结果存下来”的习惯。
2.5 排序与查找:别只会写冒泡,要懂复杂度
116 题里排序查找部分通常包含插入排序、选择排序、二分查找。很多同学抱着冒泡排序走天下,遇到需要排序的题就写个双重循环硬排。小数据量没问题,一旦 n 到 10000 以上,冒泡的 O(n²) 复杂度直接让你超时。这个时候要学的是 qsort 或手写快排。我个人的建议是:理解冒泡和选择,但实际提交尽量用qsort,C 标准库自带,代码量小且效率高。
#include <stdio.h> #include <stdlib.h> int cmp(const void *a, const void *b) { // 升序排序:返回负数表示 a 排在 b 前面 return (*(int *)a - *(int *)b); } int main() { int arr[1000], n; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &arr[i]); } qsort(arr, n, sizeof(int), cmp); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }qsort的四个参数分别是待排序数组、元素个数、单个元素字节数、比较函数指针。比较函数返回负值表示第一个参数应排在第二个前面,返回正值则相反。这里把void *强转成int *再解引用是最标准的写法,漏了强转编译直接报错。如果你不想用 qsort,手写快排也行,但一定要注意递归深度,n 很大且数据有序时,快排会退化成 O(n²),反而更慢。这个知识点 116 题里未必直接考,但后面的题目会拿超时来逼你学会它。
3. 带着思路刷题:把“看答案”变成“对答案”的实战路径
3.1 先自己写 30 分钟,再打开答案文档
这是整份 116 题及答案 word 版最正确的用法。很多人一上来就翻答案,看完觉得自己懂了,合上文档自己写,还是卡在同一个地方。原因很简单:看答案是被动接受,自己写是主动构建,两者的神经通路完全不一样。我一般会给自己定一个规矩:一道题先独立思考 30 分钟,实在写不出来再看答案,而且看完答案之后必须自己重写一遍,不能直接复制提交。重写的过程才是真正吸收的过程,你会发现答案里很多细节是第一遍看的时候没注意的,比如为什么i要小于strlen(s)而不是小于等于。
这 30 分钟不是死磕,而是“有效尝试”。我会先读题三遍,把输入、输出、样例三个部分的关键信息圈出来,然后写一个最朴素的版本——哪怕复杂度很高、可能超时,也先写出来。等跑通样例之后,再想怎么优化。这个流程比一上来就追求最优解要实用得多,因为你连“能跑”的版本都没有,讨论“高效”没有任何意义。
3.2 每组测试数据的含义:读懂样例背后的隐含条件
NOJ 的题目描述通常会给你一两组样例,但样例只是冰山一角,评测系统里的隐藏测试数据才是真正决定你过不过的东西。比如题目说“n 个整数”,样例给的是 5 个正整数,实际测试可能包含 n=0、全部负数、最大值、重复值、异常值。你要做的是把样例当作“方向标”而不是“全部”,然后用边界值去验证自己的代码。我刷 116 题时的标准流程是:写完代码先拿样例跑,过了之后立刻补测三组自己构造的数据——最小值、最大值、边界值。
举个例子,如果题目是“求数组中最大的三个数”,你只测样例1 2 3 4 5是不够的,还要测数组长度小于 3 的情况、全部是负数的情况、有重复最大值的情况。这些额外测试有些答案文档里也不会写全,需要你自己想。这个过程特别能锻炼“测试思维”,它跟写代码一样重要,甚至会直接影响你以后做项目时的信心。自我构造测试数据时,我习惯先在旁边写注释,标明“这是为了验证什么”,这样代码的可读性也大幅提升。
3.3 编译环境与代码风格的统一:减少低级失误
每个人在本地的编译器版本不一样,有的用 Dev-C++,有的用 VS Code 配 MinGW,有的用 Code::Blocks。NOJ 服务器上运行的编译器和本地环境可能不同,比如本地默认支持 C11,但 NOJ 用的还是老一点的 GCC 配置。因此提交之前建议做两件事:第一,把代码末尾加上return 0;,别嫌啰嗦,这能避免大多数“返回类型缺失”的编译警告;第二,尽量用标准库函数而不是自己造轮子,比如qsort、strlen、memcpy,只要参数用对,不会出现本地能跑服务器上过不了的情况。
还有一点是头文件的问题。有人图省事写#include <bits/stdc++.h>,这在某些支持 GNU 方言的编译器上能过,但 NOJ 如果用标准 C 模式编译就直接报错。保险起见,用到什么就#include什么,stdio.h、stdlib.h、string.h各管各的,别搞一个大杂烩。这类问题在答案文档里也会出现,有些流传的答案是老学长用旧编译器写的,拿到新版本编译器下可能有一堆 warning,不是答案错了,而是环境不一致。遇到这种情况,优先看思路,别死磕编译错误。
3.4 答案不是唯一解:一题多解的价值在哪里
同一道题,用循环能解,用递归也能解,用公式可能一行就搞定。116 题及答案 word 版给出的答案通常只是其中一种写法,不代表最优解,更不代表唯一解。我刷题最大的收获之一,就是学会“对比答案和自己写法的差异”。比如求 1 到 n 的累加和,大多数人写循环,但数学公式n*(n+1)/2一行就能算完,效率和代码简洁度都碾压循环。答案文档里可能只给了循环版本,这时候你要自己想:公式法为什么也是对的?n 很大时用 int 会不会溢出?这样一想,你对这道题的理解就不一样了。
一题多解还有一个实际好处:当某一种解法死活过不了时,换一种思路往往能绕过去。比如递归爆栈了,换成循环迭代;排序超时了,换成计数排序或 qsort。这个“换路”的能力不是靠背答案得来的,而是靠日常积累“这道题还有哪些解法”的意识。我一般会在每道题下面留一个空白,把看到答案后想到的第二种解法简单写两句,等复习的时候再看,效果比反复抄答案好得多。
4. 避坑指南:用“116 题及答案 word 版”最容易翻车的 4 个地方
4.1 只抄答案不改代码,换一个题设就挂
现象:把答案文档里的代码原封不动复制粘贴提交,系统返回答案错误或编译错误,甚至同一道题换个输入范围就超时。原因:你只复制了代码,没有理解变量名和题意之间的对应关系,局部变量大小、数组长度、判断条件都是对着原题写的,套到变体题上自然出错。解决:复制之后通读一遍,把数组长度改成题目要求的最大值,把循环条件的数字改成变量n,把输出格式对照题目检查一遍,确认不是答案本身的问题再提交。说白了,答案可以跳板,但不能当拐杖。
4.2 文档里的代码和服务器编译器不兼容
现象:本地 Dev-C++ 编译运行都正常,复制到 NOJ 上提交却报编译错误。原因:答案文档是老学长用老版本编译器写的,有些写法不在新的 C 标准里,比如隐式类型转换、未声明的函数、main前面没有int。解决:把文档代码复制到本地新建文件,开最高警告级别重新编译一遍,把每个 warning 都当错误处理。报错的常见点包括“找不到头文件”“函数未声明”“变量重复定义”,改完再提交。遇到这种情况别慌,先看错误提示的第一行,它通常精确告诉你在第几行出了问题。
4.3 超时不自知,重复提交浪费时间
现象:代码逻辑没问题,样例也能跑通,但 NOJ 返回时间超限,你反复提交几次都是同样结果。原因:你的算法复杂度太高,测试数据比你想象的大,循环套循环跑不完。解决:先算一下数据规模。如果 n 是 10^5,O(n²) 在 1 秒内已经非常勉强,O(n) 才是安全选择;如果 n 是 10^9,O(n) 也得优化。超时题目的标准应对方案是:考虑前缀和、差分、二分、哈希表等 O(n) 或 O(n log n) 的优化思路。答案文档里如果给的是朴素解法,你可以自己在旁边标注“大数据会超时,建议优化”,这个思考过程比答案本身更值钱。
4.4 输入输出格式的隐蔽坑:空格、换行、大小写
现象:本地输出控制台显示正常,一到 NOJ 就报格式错误。原因:OJ 比对输出时,空格和换行都算字符,多打一个空格、少一个换行、大小写不统一,都会判格式错误。解决:提交前反复检查printf里的格式串,尤其是“输出每个数占一行”还是“输出一行空格隔开”这种关键差异。答案文档里如果有样例输出,把样例输出复制到本地,和你的运行结果做文本对比,我用的是在终端里输完./a.out > out.txt,再用diff -u out.txt sample.txt对比,任何细微差异都会显示出来。这个习惯帮我至少挡下了十几次格式错误。
5. 把 116 题榨干:从“刷完”到“刷透”的三个进阶动作
5.1 版本化做题记录:用表格管理每道题的状态
我用一个表格跟踪每题的进度,包括“完成日期”“第一遍是否通过”“是否超时”“是否看过答案”“有没有写第二种解法”。这个表格不用很复杂,三列五列就够,但它能帮你看到自己卡在哪个类型上。我刷完前 30 道时统计过一次,发现数组类的题几乎全是第一次就过了,字符串类的题有一半看了答案才写出来。于是后面所有字符串题我都额外多照着答案敲一遍,效果立竿见影。这种基于数据的自我反馈比任何教程都直接。
| 题号范围 | 主要题型 | 我的典型失误 | 改进方法 |
|---|---|---|---|
| 1-30 | 输入输出、分支循环 | 多组输入遗漏 | 固定用while(scanf()!=EOF) |
| 31-60 | 数组、字符串 | 下标越界、strlen误用 | 数组多开一位,循环条件写i < len |
| 61-90 | 函数、递归 | 递归无终止条件、超时 | 先写终止条件,再加记忆化 |
| 91-116 | 排序、查找、综合 | 复杂度超限、格式化输出 | 用qsort、提交前文本 diff |
5.2 错误日志远比答案文档更有用
每次提交失败后,我会把 NOJ 返回的错误类型记在题目旁边:编译错误、答案错误、时间超限、内存超限、格式错误哪一个。记录两周后你会发现,自己犯的错误高度集中在某两类,而不是均匀分布。我当年就是反复在“格式错误”和“答案错误”之间横跳,后来才总结出“多组输入漏处理”这个主因,改掉之后整体通过率飙升。这份错题日志相当于你自己给自己画的“能力地图”,比任何答案文档都有针对性。
5.3 把每道题的收获压缩成一段话
最后 116 题不需要每一题都留着代码,你要留的是“这道题让我学会了什么”。我自己的习惯是每道题在表格的备注栏写一句话,比如“多组输入要用 EOF 判断”“递归要加记忆化”“排序用 qsort 最省事”。复习的时候只看这些备注,不重新读代码。这个方法让我期末前能在一小时内过完全部题型,而不是从头看代码看到眼花。一份答案 word 文档的价值,只有在“你先写、对比、记录、总结”这个循环里才能真正释放出来,否则它只是一堆躺在磁盘里的字符。
我在 NOJ 上付费最大的教训就是:答案能让你过这一道题,却没法帮你过下一道长得不一样的题。唯一能带走的,是你自己在踩坑、对比、改错中形成的判断力。抱着这份 116 题的文档,好好用上面的方法刷一遍,把它从“答案集”变成“错题本”,C 语言这门课的底子就稳了。希望帮到你。
本文还有配套的精品资源,点击获取