ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

西工大NOJ 116题刷题攻略:从边界条件到算法优化

西工大NOJ 116题刷题攻略:从边界条件到算法优化 简介一份覆盖西北工业大学在线编程比赛NOJ116道真题及解答的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)如果没有 memofib(3) 会被重复算好几遍有了 memo第二次遇到 fib(3) 直接返回存好的值整个计算时间从指数级降到线性。这也解释了为什么同样的思路有人过了有人超时差的不是智商而是“有没有把算过的结果存下来”的习惯。2.5 排序与查找别只会写冒泡要懂复杂度116 题里排序查找部分通常包含插入排序、选择排序、二分查找。很多同学抱着冒泡排序走天下遇到需要排序的题就写个双重循环硬排。小数据量没问题一旦 n 到 10000 以上冒泡的 O(n²) 复杂度直接让你超时。这个时候要学的是 qsort 或手写快排。我个人的建议是理解冒泡和选择但实际提交尽量用qsortC 标准库自带代码量小且效率高。#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 个正整数实际测试可能包含 n0、全部负数、最大值、重复值、异常值。你要做的是把样例当作“方向标”而不是“全部”然后用边界值去验证自己的代码。我刷 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*(n1)/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^5O(n²) 在 1 秒内已经非常勉强O(n) 才是安全选择如果 n 是 10^9O(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 len61-90函数、递归递归无终止条件、超时先写终止条件再加记忆化91-116排序、查找、综合复杂度超限、格式化输出用qsort、提交前文本 diff5.2 错误日志远比答案文档更有用每次提交失败后我会把 NOJ 返回的错误类型记在题目旁边编译错误、答案错误、时间超限、内存超限、格式错误哪一个。记录两周后你会发现自己犯的错误高度集中在某两类而不是均匀分布。我当年就是反复在“格式错误”和“答案错误”之间横跳后来才总结出“多组输入漏处理”这个主因改掉之后整体通过率飙升。这份错题日志相当于你自己给自己画的“能力地图”比任何答案文档都有针对性。5.3 把每道题的收获压缩成一段话最后 116 题不需要每一题都留着代码你要留的是“这道题让我学会了什么”。我自己的习惯是每道题在表格的备注栏写一句话比如“多组输入要用 EOF 判断”“递归要加记忆化”“排序用 qsort 最省事”。复习的时候只看这些备注不重新读代码。这个方法让我期末前能在一小时内过完全部题型而不是从头看代码看到眼花。一份答案 word 文档的价值只有在“你先写、对比、记录、总结”这个循环里才能真正释放出来否则它只是一堆躺在磁盘里的字符。我在 NOJ 上付费最大的教训就是答案能让你过这一道题却没法帮你过下一道长得不一样的题。唯一能带走的是你自己在踩坑、对比、改错中形成的判断力。抱着这份 116 题的文档好好用上面的方法刷一遍把它从“答案集”变成“错题本”C 语言这门课的底子就稳了。希望帮到你。本文还有配套的精品资源点击获取
返回列表