NUIST程序设计基础OJ题库:从WA到AC的判题原理与避坑指南 简介这份资源是南京信息工程大学NUIST程序设计基础课程的OJ题库配套资料面向正在学习C或Java编程、准备算法训练与课程实践的学生也适合希望系统刷题巩固数据结构与算法设计能力的自学者。压缩包为zip格式整体约482.72MB文件总数与类型明细上游暂未提供但结合OJ题库场景通常包含源代码文件、测试数据及编译运行结果等工作目录内容便于回顾解题思路与调试过程。题库覆盖排序、二分查找、深度优先与广度优先搜索、最短路径、最小生成树、动态规划等经典算法并涉及数组、链表、栈、队列、二叉树、哈希表等数据结构可帮助读者在实时评测反馈中快速定位错误、优化时间与空间复杂度。目前已有751人学习下载适合作为课程作业练习、竞赛入门与算法查漏补缺的实战素材。1. 程序设计基础 OJ 题库 NUIST从“能跑”到“能过”的那道坎很多同学第一次在 NUIST 的程序设计基础课上打开 OJ 题库都会经历同一个瞬间本地 Dev-C 里跑得好好的代码提交上去却是一个红色的 Wrong Answer或者更让人摸不着头脑的 Runtime Error。这不是玄学而是 OJ 在线判题系统和本地编译器的运行环境、输入输出规范、边界处理逻辑存在系统性差异。程序设计基础 OJ 题库 NUIST 这套题目的价值恰恰在于它把 C 语言和 C 程序设计里最容易翻车的那些点——多组输入、格式敏感、数组越界、整数溢出——全部压缩进了几十道循序渐进的题目里。它适合刚学完语法、想通过 oj 刷题把知识变成肌肉记忆的初学者也适合想拿它当教学案例的助教。接下来的内容我会按“判题原理 → 环境搭建 → 题型拆解 → 避坑排查 → 进阶技巧”的顺序把从零到稳定 AC 的路径讲清楚。2. OJ 判题系统到底在背后做了什么输入输出重定向与比对逻辑2.1 从点击“提交”到返回结果中间经历了什么很多人把 OJ 当成一个黑匣子觉得 AC 或 WA 是运气。其实主流 OJ 的判题流程非常固定你的源代码被上传到服务器后判题机先调用编译器通常是 gcc 或 g把它编译成可执行文件编译失败直接返回 Compilation Error。编译成功后判题机把题目预设的输入数据文件通过标准输入stdin喂给你的程序同时把你的程序输出重定向到一个临时文件最后拿这个临时文件和标准答案文件做逐字节或逐行比对。比对方式决定了你需要注意什么如果是逐字节比对行末多一个空格都会 WA如果是忽略行末空格的比对容错率稍高但换行符数量仍然敏感。NUIST 的程序设计基础题库大多采用标准 ACM 赛制判题逻辑也就是“读入一组数据、输出一组结果、循环直到输入结束”。这意味着你的程序不能只处理一组输入就退出也不能在输出里加任何提示性文字比如printf(请输入n:)这种在本地调试时很舒服的写法提交上去必错。判题机不会看你的提示语它只认标准输出里的数字和字符。另一个容易被忽略的点是时间限制和空间限制。程序设计基础题目的时间限制通常在 1 秒到 2 秒之间空间限制在 32MB 到 256MB 之间。C 语言里一个int占 4 字节开一个int a[1000000]就是 4MB看起来不大但如果你在函数内部开这么大的数组栈空间可能直接爆掉返回 Runtime Error。常见做法是把大数组定义成全局变量或者用static修饰让它落在静态存储区而不是栈上。2.2 本地复现判题环境的最小操作要减少“本地能跑、提交就挂”的情况最有效的办法是在本地模拟判题机的输入输出方式。具体操作是把你的程序编译成可执行文件然后用文件重定向的方式喂输入、接输出再和手算结果对比。# 假设你的源代码文件是 main.c gcc main.c -o main -O2 -Wall # 手动构造一个输入文件 input.txt内容按题目要求写 # 然后运行程序把输出重定向到 output.txt ./main input.txt output.txt # 查看输出结果 cat output.txt这段命令里-O2是开启编译器优化NUIST 判题机通常也会开优化本地不开可能导致某些依赖未定义行为的代码在本地“碰巧正确”、提交后出错。-Wall是打开所有警告很多边界问题编译器早就提醒你了只是你忽略了警告信息。 input.txt和 output.txt分别代表标准输入重定向和标准输出重定向这是判题机最核心的动作本地模拟一遍就能提前发现“多组输入没循环”“输出多打了空格”这类低级错误。参数方面-O2不是必须的但建议加上因为它能暴露一些优化后才出现的未定义行为。-Wall强烈建议常开尤其是初学阶段编译器警告就是你的免费代码审查员。如果你用的是 C把gcc换成g文件后缀改成.cpp其余逻辑完全一样。提示NUIST 题库里部分题目明确要求“多组输入”但题目描述可能只在一开始提一句后面不再重复。看到“输入包含多组测试数据”或“输入直到文件结束”这类字眼就必须用while(scanf(...) ! EOF)或while(cin n)的结构。2.3 输入输出格式的敏感度为什么多一个空格就 WAOJ 的比对逻辑决定了输出格式的容错率极低。以一道典型的“输出两个数的和”为例标准答案文件里写的是3\n你的程序输出3 \n数字后面多一个空格逐字节比对就会失败。更隐蔽的情况是行末换行符有些题目要求最后一行输出后也有换行有些题目则对末尾换行不敏感但你不能赌最稳妥的做法是严格按照题目样例的格式来。常见做法是每道题先在本地把样例输入跑一遍用cat -A output.txt查看输出的不可见字符。cat -A会把换行显示为$把制表符显示为^I这样你就能确认自己的输出和样例是否完全一致。这个习惯能帮你省下大量“明明答案对但就是 WA”的调试时间。另一个高频翻车点是浮点数输出。题目要求“保留两位小数”你就必须用printf(%.2f, ans)不能用cout ans默认输出。C 里可以用cout fixed setprecision(2) ans但需要包含iomanip头文件。浮点数的比对通常有精度容差但输出格式本身仍然是严格的该保留几位就保留几位不能多也不能少。3. NUIST 题库高频题型拆解从循环到数组的 AC 路径3.1 多组输入的三种写法与选择依据NUIST 程序设计基础题库里多组输入是贯穿始终的考点。根据题目给出的输入终止条件不同写法分为三类。第一类明确告诉你有多少组数据第一行是组数 T。这种最简单直接循环 T 次。#include stdio.h int main() { int t; scanf(%d, t); // 读取组数 while (t--) { // 循环 t 次 int a, b; scanf(%d %d, a, b); printf(%d\n, a b); } return 0; }这段代码的逻辑是先读组数t然后while(t--)会执行t次每次读一对整数并输出和。参数上t--是后置自减先判断t是否为真非零再减一所以循环次数正好是t次。注意输出必须带\n否则多组结果会挤在一行。第二类输入直到文件结束EOF没有明确的组数。这是 NUIST 题库里最常见的多组输入形式。#include stdio.h int main() { int a, b; while (scanf(%d %d, a, b) ! EOF) { // 读到文件结束 printf(%d\n, a b); } return 0; }scanf的返回值是成功读入的变量个数当读到文件末尾时返回EOF通常是 -1。while(scanf(...) ! EOF)就是“只要能读到两个整数就继续”。更简洁的写法是while(~scanf(%d %d, a, b))利用按位取反但可读性不如显式写! EOF初学阶段建议用后者。第三类输入以特定值结束比如“输入 0 0 表示结束”。这种需要在循环内部判断。#include stdio.h int main() { int a, b; while (scanf(%d %d, a, b) 2) { // 确保读到了两个数 if (a 0 b 0) break; // 遇到 0 0 退出 printf(%d\n, a b); } return 0; }这里scanf(...) 2比! EOF更严谨因为如果输入中途出现非数字字符scanf可能返回 1 或 0用 2能确保两个变量都成功赋值。break直接跳出循环不再处理后续输入。3.2 数组类题目的边界处理越界与初始化的血泪经验数组是程序设计基础里最容易出 Runtime Error 的地方。NUIST 题库里常见的数组题包括“求最大值最小值”“数组逆序”“统计字符出现次数”等。这类题目的核心坑点有三个数组开多大、下标从几开始、要不要初始化。以“统计一段字符串中每个字母出现的次数”为例标准做法是开一个大小为 26 的计数数组初始化为 0然后遍历字符串用count[str[i] - a]累加。#include stdio.h #include string.h int main() { char str[1005]; // 开足够大的字符数组 int count[26] {0}; // 计数数组必须初始化为 0 scanf(%s, str); // 读入字符串不含空格 int len strlen(str); for (int i 0; i len; i) { if (str[i] a str[i] z) { count[str[i] - a]; // 映射到 0~25 } } for (int i 0; i 26; i) { if (count[i] 0) { printf(%c: %d\n, a i, count[i]); } } return 0; }这段代码里char str[1005]的大小是根据题目数据范围定的如果题目说字符串长度不超过 1000那开 1005 是留了余量。int count[26] {0}的 {0}不能省否则数组里是随机值累加结果完全错误。str[i] - a是把小写字母映射到 0 到 25 的下标前提是你已经用if判断了字符范围否则遇到大写字母或数字会越界访问count数组直接 Runtime Error 或 WA。另一个高频错误是字符串输入用scanf(%s, str)时遇到空格会停止读取。如果题目输入的字符串包含空格必须用fgets(str, sizeof(str), stdin)或gets不推荐已废弃。fgets会把换行符也读进去需要手动处理掉末尾的\n。注意NUIST 题库里有些题目会给出“数据范围”提示比如“1 ≤ n ≤ 1000”。这个范围就是你开数组的依据不要凭感觉开。开小了越界开大了浪费空间极端情况下可能超出空间限制。3.3 循环与条件判断的常见逻辑陷阱循环和条件判断本身不难但在 OJ 环境下几个细节会反复坑人。第一个是for循环的边界for (int i 0; i n; i)和for (int i 1; i n; i)都能遍历 n 个元素但如果你在循环体内同时用了i作为数组下标和计数变量混用两种风格就会出错。建议统一用0起始的下标风格和 C 语言数组的默认下标一致。第二个是while循环里的变量更新。比如“求一个数的各位数字之和”常见写法是while (n 0) { sum n % 10; n / 10; }。如果漏掉n / 10就是死循环判题机会返回 Time Limit Exceeded。死循环在本地跑的时候可能因为输入数据小、你手动中断了而没发现提交后必 TLE。第三个是if-else链的顺序。比如“判断成绩等级”如果先判断score 60输出“及格”再判断score 90输出“优秀”那 90 分以上的成绩会先命中“及格”分支永远到不了“优秀”。正确的顺序是从高到低判断或者用else if把条件写完整。这类逻辑错误在本地用样例测试时可能刚好没覆盖到边界值提交后遇到边界数据就 WA。4. 避坑与排查NUIST OJ 提交后不 AC 的 5 个真实原因4.1 现象本地输出和样例一模一样提交却是 Wrong Answer原因通常不是答案算错了而是输出格式有不可见差异。最常见的是行末多了空格、少了换行或者多组数据之间多输出了空行。NUIST 题库的比对逻辑对格式敏感样例里数字之间是空格你就不能用制表符样例最后一行有换行你就不能省。解决方法是把样例输入保存成input.txt运行./main input.txt output.txt然后用cat -A output.txt查看不可见字符和题目样例的cat -A结果逐行对比。另一个办法是用diff命令把标准答案保存成answer.txt运行diff output.txt answer.txt有差异会直接标出来。4.2 现象程序在本地跑得好好的提交后 Runtime Error原因大概率是数组越界或除以零。NUIST 题库里数组题的数据范围有时会在题目描述的最后一行给出容易被忽略。比如题目说“字符串长度不超过 100”你开了char str[100]但字符串末尾还需要一个\0实际需要 101 个字节开 100 就越界了。常见做法是在题目给定范围的基础上加 5 到 10 的余量。除以零的情况常出现在“求平均值”类题目里如果输入数据可能为 0 个元素你的sum / n就会触发除零错误。提交前检查所有除法运算的分母是否可能为 0必要时加if判断。4.3 现象多组输入的题目只输出了一组结果原因是循环结构写错了。有人用if代替while有人写了while但循环体内没有重新读取输入导致死循环或只处理一次。还有一种隐蔽情况用了scanf但没检查返回值输入读到 EOF 后a和b保持上一次的值程序继续用旧值计算并输出多出一组错误结果。解决方法是严格按照题目给出的终止条件选择循环写法。不确定的时候把三种多组输入模板都试一遍看哪种能过。NUIST 题库的题目描述里通常会有“输入包含多组测试数据”或“输入以 EOF 结束”的明确提示仔细读题就能确定。4.4 现象整数运算结果不对但小数据测试正常原因是整数溢出。C 语言里int通常是 32 位能表示的范围大约是 -21 亿到 21 亿。如果题目涉及阶乘、斐波那契数列、大数加法中间结果很容易超过这个范围。比如计算 20 的阶乘结果约 2.4×10^18远超int范围甚至超过long long的范围约 9.2×10^18。解决方法是根据题目数据范围选择合适的数据类型。long long能覆盖大多数基础题目的需求输出时用printf(%lld, ans)。如果题目明确要求处理超大数比如 100 的阶乘就需要用数组模拟大数运算这是 NUIST 题库里少数几道拔高题的内容。4.5 现象编译错误提示看不懂反复提交都 CE原因可能是用了不支持的编译器特性或者头文件没包含。NUIST 题库的 C 语言编译器通常是 gccC 是 g标准版本可能是 C99 或 C11。如果你在 C 语言里写了//注释C99 支持但极老编译器可能不支持或者用了for (int i 0; ...)这种在循环内声明变量的写法C99 支持一般没问题。但如果你用了 C 的cin/cout却提交到 C 语言题目就会 CE。解决方法是先看编译错误的第一条通常第一条错误会引发后续一连串错误修好第一条后面的可能自动消失。常见 CE 原因包括漏分号、括号不匹配、变量名拼写错误、用了未声明的函数。把本地编译器的警告级别开到-Wall大部分问题在本地就能发现。5. 从 AC 到稳定 ACNUIST 题库的进阶刷题习惯5.1 用对拍脚本验证边界数据当你觉得自己的代码逻辑没问题但提交后就是 WA而样例又太弱测不出问题时对拍是最有效的排查手段。对拍的核心思路是写一个暴力解法保证正确但可能超时再写一个随机数据生成器让两个程序跑同一组随机数据比较输出是否一致。# 假设你有一个暴力解法 brute.c 和你的解法 main.c gcc brute.c -o brute -O2 gcc main.c -o main -O2 # 写一个数据生成器 gen.c编译成 gen gcc gen.c -o gen -O2 # 循环对拍 1000 次 for i in $(seq 1 1000); do ./gen input.txt # 生成随机输入 ./brute input.txt brute_out.txt ./main input.txt main_out.txt if ! diff -q brute_out.txt main_out.txt /dev/null; then echo 差异出现在第 $i 组数据 cat input.txt break fi done这段脚本的逻辑是每次生成一组随机输入分别喂给暴力解和你的解用diff -q静默比较两个输出文件。如果不同就打印出导致差异的输入数据你拿着这组数据去调试比盲目改代码高效得多。参数上seq 1 1000是生成 1 到 1000 的序列对拍次数根据题目复杂度调整简单题 100 次就够复杂题可以到 10000 次。数据生成器gen.c需要根据题目输入格式来写用rand()生成随机数注意控制数据范围覆盖边界值比如最小值、最大值、特殊值0、负数。对拍脚本在 Linux 和 macOS 的终端里可以直接跑Windows 下用 Git Bash 或 WSL 也能运行。5.2 建立自己的题型索引与代码模板库刷 NUIST 题库到一定数量后你会发现题目类型是有限的多组输入求和、数组最值、字符串统计、简单排序、素数判断、进制转换。每类题目都有固定的代码骨架把这些骨架整理成模板下次遇到同类题目直接套用能大幅减少调试时间。我自己的习惯是建一个templates目录里面按题型存.c文件每个文件只保留核心逻辑变量名用通用写法。比如multi_input_sum.c存多组输入求和的模板array_max_min.c存数组最值的模板。每次遇到新题目先判断属于哪类复制模板改逻辑而不是从空白文件开始写。这个习惯让我在 NUIST 题库上的 AC 率从“看运气”变成了“基本稳定”。另一个习惯是每道 AC 的题目都保留一份带注释的代码注释里写清楚这道题的坑点是什么、用了什么技巧。过一段时间回头看这些注释就是最好的复习材料。程序设计基础的题目不难但坑点密集把踩过的坑记下来比反复刷同类型的题更有价值。5.3 时间限制与算法选择的平衡NUIST 题库里大部分题目用暴力循环就能过但少数题目会卡时间复杂度。比如“判断一个数是否为素数”如果对每个数都从 2 循环到 n-1当 n 接近 10^9 时必然超时。常见做法是只循环到 sqrt(n)因为如果 n 有大于 sqrt(n) 的因子必然有小于 sqrt(n) 的对应因子。#include stdio.h #include math.h int is_prime(int n) { if (n 2) return 0; // 0 和 1 不是素数 for (int i 2; i (int)sqrt(n); i) { if (n % i 0) return 0; // 找到因子不是素数 } return 1; }这段代码里sqrt(n)是数学库函数需要包含math.h编译时可能需要加-lm链接数学库。(int)sqrt(n)把浮点结果转成整数避免浮点比较的精度问题。循环上界用i (int)sqrt(n)而不是i * i n后者在i很大时可能溢出前者更安全。对于更复杂的题目比如“求第 n 个斐波那契数模 10007”如果 n 达到 10^9就需要用矩阵快速幂这是程序设计基础课程之外的进阶内容。NUIST 题库里这类题目通常会有额外提示比如“本题需要 O(log n) 算法”看到这种提示就要意识到暴力解法过不了需要换思路。刷题到后期你会逐渐形成一种直觉看到题目数据范围就能判断该用什么复杂度的算法。数据范围在 10^3 以内O(n^2) 通常安全10^5 以内需要 O(n log n)10^9 以上必须 O(log n) 或 O(1)。这个直觉不是天生的是踩了足够多次 TLE 之后长出来的。希望这些经验能帮你在 NUIST 的程序设计基础 OJ 题库上少走几个弯路把时间花在真正理解算法上而不是和判题机斗智斗勇。本文还有配套的精品资源点击获取