简介:程序设计基础在线判题题库(南京信息工程大学)是一套面向高校程序设计初学者的训练资源,聚焦基础知识、数据结构与算法设计,帮助学习者通过在线评测平台的大量编程实战巩固所学内容。压缩包整体约483MB,内部资料围绕题库与工作空间展开,涵盖排序、搜索、图论最短路径与最小生成树、动态规划等经典题型,并涉及数组、链表、栈、队列、树、哈希表等常用数据结构的使用。已有751人学习使用,适合正在修读程序设计基础课程、准备算法入门或需要系统刷题的学生。通过学习这份资料,读者可以获得完整的在线判题题库练习素材,借助工作空间文件对照检查代码运行结果与复杂度,回顾解题思路、排查错误,进而有效提升独立分析和编程实现能力。
1. OJ 题库不是算法竞赛,是程序设计基础这门课的“测谎仪”
一门程序设计基础课,对学生最大的冲击往往不是语法本身,而是第一次把代码交到 OJ 上就被判 WA。这份某高校程序设计基础 OJ 题库,从顺序结构一路排到结构体与指针,把每个语法点拆成可提交、可判定的题目,适合大一跟课刷题、期末突击复习,也适合想快速验证自己代码能力的初学者。它跟算法竞赛题单是两回事:同样是 OJ 算法题,这套题库更看重基本语法、输入输出规约和边界意识,而不是脑筋急转弯式的套路。对刚接触 OJ 算法的新手来说,它最大的价值是给你一条按章节递进的主线,让你知道每天该干什么、写到哪一步才算真的会了。
2. 题库结构:从顺序结构到结构体的完整训练链
2.1 题目编排:一门课被拆成可按周推进的小目标
我拆这套题库时的第一感受是:它不是把所有题胡乱扔在一起,而是严格按程序设计基础的教学节奏分块。前几周是顺序结构和分支结构,题目很简单,比如温度转换、判断闰年、成绩等级,目的是让人先学会“把自然语言翻译成 C 表达式”。中间是循环和数组,题目开始有重复计算的味道,比如累加求和、冒泡排序、逆序输出。再往后是函数、递归、字符串、结构体,题目开始要求你拆分功能、处理复杂数据。
这种编排的最大好处是:你不需要自己决定今天该练什么。课程讲到哪个阶段,你就去刷对应模块,难度刚好卡在“需要动脑但不至于劝退”的位置。以我这里某高校的版本为例,题量覆盖两学期完全够用,每章大概有基础题、进阶题和少量综合题的区分,基础题通常是单知识点,进阶题会把两个语法点组合起来,综合题则接近期末考试难度。
| 训练模块 | 常见题型 | 最容易翻车的点 |
|---|---|---|
| 顺序结构 | 求和、单位换算、温度转换 | scanf 漏写取地址符 |
| 分支结构 | 成绩等级、闰年判断、分段函数 | else if 逻辑边界重叠 |
| 循环结构 | 阶乘、累加、图形打印 | 循环变量末值理解错 |
| 数组 | 逆序、去重、统计 | 下标越界、循环边界写错 |
| 函数与递归 | 素数判断、汉诺塔、斐波那契 | 参数传递和递归出口 |
| 字符串 | 复制、拼接、单词统计 | 忘记字符串结尾的 \0 |
| 指针 | 交换、动态分配 | 野指针、取地址与解引用混用 |
| 结构体 | 成绩排序、学生信息管理 | 结构体变量直接比较、排序规则写反 |
刷题节奏上,我一般建议学生每章至少过两遍:第一遍老老实实按顺序写,第二遍只挑自己出过错或没思路的题重做。题库里综合题不需要全部做完,挑 5 到 8 道能拉伸思维的即可,把时间留给复盘比盲目堆题量更值。
2.2 A+B 和水仙花数:入门题里藏着输入输出纪律
先看一道几乎所有 OJ 都有的 A+B 题。别觉得它简单,在支持多组输入的判题系统里,这题能卡住不少第一次用 OJ 的人:
#include <stdio.h> int main() { int a, b; while (scanf("%d %d", &a, &b) != EOF) { printf("%d\n", a + b); } return 0; }逻辑说明:scanf的返回值是成功读入的变量个数,当输入流结束时返回EOF,所以while (scanf(...) != EOF)表示一直读到文件尾,这是 OJ 上“多组测试数据”的标准写法。如果不加这个循环,程序只处理一组数据就退出,评测机剩余测试用例全部空跑,结果自然是 WA 或 RE。
参数说明:%d %d之间那个空格不影响输入读取,scanf默认跳过空白字符;但输出必须是printf("%d\n", ...),也就是每个结果占一行,不能把结果挤在同一行里。第一题的意义就在于此,让你先建立“输入未知组数、输出逐行对齐”的肌肉记忆。
再看水仙花数,这题考的是取位和循环:
#include <stdio.h> int main() { int i, a, b, c; for (i = 100; i <= 999; i++) { a = i / 100; // 百位 b = i / 10 % 10; // 十位,先除 10 再取余 c = i % 10; // 个位 if (a * a * a + b * b * b + c * c * c == i) { printf("%d\n", i); } } return 0; }逻辑说明:水仙花数是三位数,所以循环范围限定为 100 到 999。取百位做整除,取十位先除以 10 扔掉个位再对 10 取余,取个位直接对 10 取余。三个位数的立方和等于原数就输出。
这里有个容易踩的坑:有人为了“性能”在找到第一个水仙花数后直接用break退出循环,结果只输出了一个数。题目要求的是输出所有满足条件的数,而不是“找到就停”。入门题看起来简单,实际上是在训练你读题的能力——输出是逐行还是同行、要不要排序、区间是闭区间还是开区间,这些细节决定生死。
2.3 数组、字符串和结构体:中段题的三种典型出法
题库进入数组模块后,风格会明显变化:不再是一两行就能写完的代码,而是需要先想清楚下标关系。比如“将数组元素逆序输出”这类题,错误写法里最常见的不是逆序逻辑本身,而是数组下标从 1 开始还是从 0 开始没统一,导致最后一个元素丢数据或者越界访问。
字符串模块会引入字符数组和指针的混用。题目往往长这样:给一句英文,统计单词个数,或者把两个字符串拼接起来。这类题的核心纠缠点在\0——字符数组必须以\0结尾,printf("%s")才能知道到哪里停止。很多人用gets读入后忘记处理换行符,或者手动拼接时没有在末尾补\0,输出就变成一串乱码。
结构体模块里排序是重头戏,尤其配合qsort使用时,比较函数的写法要格外小心:
#include <stdio.h> #include <stdlib.h> typedef struct { char name[32]; int score; } Student; int cmp(const void *a, const void *b) { return ((Student *)b)->score - ((Student *)a)->score; } int main() { Student list[100]; int n = 0, i; // 读入 n 个学生,此处省略录入代码 qsort(list, n, sizeof(Student), cmp); for (i = 0; i < n; i++) { printf("%s %d\n", list[i].name, list[i].score); } return 0; }逻辑说明:qsort需要传入数组首地址、元素个数、单个元素字节数和比较函数。比较函数返回负数、零、正数分别表示前者小于、等于、大于后者;这里写成b->score - a->score是降序,如果想升序就反过来写a->score - b->score。void *参数必须强转成Student *再取成员,否则编译器报不完整类型错误。
从这节开始,调试能力变得比写代码能力更重要。我的习惯是:遇到结构体排序题,先在纸上画一个 3 元素的小数组,手动跑一遍比较函数,确认排序方向对了再提交。题库里这类题数量不少,练熟之后,期末考试最后一道“学生成绩管理系统”大题基本就稳了。
3. 读懂判题规则:输入输出格式决定你的一血能不能交上
3.1 多组测试数据:三种最常见读入模式
OJ 题和平时在 IDE 里写题最大的区别在于“数据是批量灌进来的”。判题机不会只测你提交时填的那组数据,它会用多组数据反复运行你的程序。新手最容易翻车的,就是只写了处理一组数据的代码。我归纳了下,这套题库里常见读入模式就三种。
第一种是 EOF 模式,也就是不知道有多少组数据,读到文件结束为止。写法和 2.2 节 A+B 一样,用while (scanf(...) != EOF)。第二种是 T 组模式,第一行告诉你有几组测试数据,后面每组按格式给:
#include <stdio.h> int main() { int T, i; scanf("%d", &T); for (i = 0; i < T; i++) { int n, sum = 0, x, j; scanf("%d", &n); // 本组有 n 个数 for (j = 0; j < n; j++) { scanf("%d", &x); sum += x; } printf("%d\n", sum); } return 0; }逻辑说明:先读入组数T,外层循环跑T次,每组内部先读该组元素个数n,再循环读入并累加。这种模式在题库里常用于“连续整数求和”“平均数计算”等题目。
参数说明:内层循环如果写成for (j = 1; j <= n; j++)也可以,但下标从 0 开始更符合数组习惯,读x用的是同一个变量,每次覆盖即可,不需要存下所有数。
第三种是哨兵模式:读入两个数,如果都是 0 就结束。这种通常出现在“计算两点距离”“求和直到输入 0 0”这类题里,核心是判断条件写在循环体前部:
while (scanf("%d %d", &a, &b) == 2 && (a != 0 || b != 0)) { // 处理数据 }如果你发现自己写出的代码只能跑通手输数据,但在 OJ 上报 PE 或 WA,首先怀疑输入模式写错了。特别是 EOF 模式写成while (1)加break的写法,虽然本地运行没问题,但在评测机上可能因多读一次变量或漏读空行而出错。
3.2 输出格式:空格、换行和浮点精度都是分数
输出格式是另一个“看起来不是问题的问题”。判题机比较的是字节流,多一个空格、少一个换行都会被判 PE,也就是格式错误。虽然 PE 在某些 OJ 上只算警告,但在这套题库里,PE 也算未通过,需要重新提交。
常见要求有这么几条:每个输出占一行,结尾没有多余空格;多行输出中间没有多余空行;按题目要求用冒号分隔时注意中英文状态;浮点数保留位数必须用printf的格式化控制。比如保留两位小数:
printf("%.2lf\n", ans);这里.2表示小数点后两位,lf对应double类型。输入用%lf,输出用%.2lf,格式串里漏写.2或写成%d都会导致 WA。还有一类题要求输出宽度,比如“输出 5 位整数,不足补零”,用%05d就可以解决。
浮点数比较也是这个模块的高频坑:不要用if (a == b)判断两个浮点数相等,编译能过,但结果几乎必然是 WA。原因很简单,浮点数的二进制表示不精确,0.1 + 0.2 不等于 0.3。正确做法是比较差的绝对值是否小于某个极小值,比如if (fabs(a - b) < 1e-8)。题库里涉及二分法求根、计算圆周率的题,基本都会在这里埋一个坑。
3.3 OJ 状态码:提交之前先看懂这份对照表
拿到一份 OJ 评测结果,先别慌着改代码,对照看是哪类问题再动手。下表是这套题库里出现频率最高的几种状态码:
| 状态码 | 全称与含义 | 第一排查方向 |
|---|---|---|
| AC | Accepted,通过 | 不需要处理,下一题 |
| WA | Wrong Answer,答案错误 | 逻辑边界、输入输出格式、整数溢出 |
| PE | Presentation Error,格式错误 | 空格、换行、冒号大小写 |
| TLE | Time Limit Exceeded,超时 | 算法复杂度、死循环 |
| MLE | Memory Limit Exceeded,内存超限 | 数组过大、递归层数过深 |
| RE | Runtime Error,运行错误 | 数组越界、除零、空指针 |
| CE | Compile Error,编译错误 | 语法、头文件、中英文符号 |
我一般会按“CE → RE → TLE → WA → PE”的顺序排查。CE 最好办,看编译日志就能定位;RE 和 TLE 一定是代码本身有问题;WA 和 PE 则需要回到题目描述重新读,尤其是输入输出要求,很多时候不是代码写错了,是题意理解偏了。这套题库没有故意刁难人的“纸老虎题”,状态码给得也比较规矩,所以你只要按这个顺序排查,大多数题都能在 10 分钟内定位到问题。
4. 避坑手册:五个最可能让心理防线崩塌的判题错误
4.1 CE:本地编译通过,OJ 上却报编译错误
现象:代码在本地 IDE 里一按运行就出结果,复制到 OJ 编辑器里提交,立刻显示 CE,编译错误日志一片红。
原因:最常见的三个来源是:第一,代码里混入了中文符号,比如全角分号、全角括号,字符在本地编译器里可能被当作非法字符或宽字符处理,而 OJ 的编译器更严格;第二,某些头文件没有引入,比如用memset却没写#include <string.h>,本地编译器因为其他头文件间接引入了它,但 OJ 环境里不会;第三,main函数写成void main()或省略返回类型,部分 OJ 的编译器默认不支持这种老式写法。
解决:先把代码里的中文标点全部替换成英文半角标点,再把所用到的每个库函数的头文件都显式写上,最后把main函数改成标准的int main(void)。我有一个习惯:每次提交前在代码里搜一遍中文符号,特别是引号和括号,往往能找到问题所在。
4.2 WA:样例全对,提交一大片红
现象:本地用题目给的示例数据测试,输出和标准输出一模一样,但点击提交后返回 WA,而且不是一两组,是一大片。
原因:示例数据只是给你验证思路的,判题用的测试数据覆盖了更多边界情况。最常见的原因是边界没处理:输入是最小值、最大值、只有一个元素、空数组等特殊情况时,你的代码会算错。比如求一组数中的最大值,有人初始化max = 0,测试数据全是正数时没问题,一旦出现负数,结果就错了。还有一类是整数溢出,累加结果超过了int能表达的 2147483647,应该用long long的场景用了int。
解决:重新读一遍题目,把输入变量的取值范围圈出来,然后写几个边界测试用例自己跑:最小值、最大值、极端的单个元素、最大数据量。如果输入是整数,就把int换成long long,输出格式串同步改成%lld。如果问题不在数据类型,用printf在关键变量处打中间值,定位是哪一步算错了。记住一个规律:WA 里超过一半的问题是边界,不是核心逻辑。
4.3 PE:答案全对,但输出多了一个空格
现象:OJ 返回的是 PE,不是 WA。你仔细对了一遍,发现数据本身是算对的,但输出的格式和标准答案有细微差别。
原因:典型的格式细节包括:行尾多了一个空格、一组数据和下一组数据之间多打了一个空行、该大写的地方写成小写、该用英文冒号的地方写成中文冒号。尤其常见的是输出数组元素时,有人在每个元素后都加了空格,包括最后一个,但题目要求元素之间用空格分隔、最后一个元素后面没有空格。
解决:把输出语句改成“第一个元素前面不加分隔符,后续元素前面加分隔符”的结构。比如输出数组:
for (i = 0; i < n; i++) { if (i > 0) printf(" "); printf("%d", arr[i]); } printf("\n");逻辑说明:if (i > 0)控制从第二个元素开始前面才补空格,这样最后一个元素后面不会有多余空格。如果你已经在用这种写法还是 PE,那就检查换行数量:每组数据之后是否都要多输出一个空行,题目描述里一般写得很清楚,只有明确说“每组输出后跟一个空行”才需要加。宁可少加一个换行,也不要多加,因为多一个空行必然 PE。
4.4 TLE 和 MLE:超时和超内存不是玄学,是复杂度失控
现象:代码逻辑看起来没问题,小数据量测试都过了,但提交后 TLE,运行超时。或者某些题一旦开了一个很大的数组,直接报 MLE。
原因:TLE 最常见的是算法复杂度太高。比如一组数据规模是 10 万,你的冒泡排序是 O(n²),需要算 100 亿次,判题机的时间限制通常只有 1 到 2 秒,铁定超时。另一种是死循环,比如while的终止条件永远不为假,这种情况直接消耗完整时限然后超时。MLE 则通常是数组开得太大,比如题目要求 100 个元素,你开了一个 100 万长度的int数组,那就是 4MB 内存起步,碰上内存限制只有 32MB 的题目,再加上其他变量,很容易超限。
解决:先看输入数据的范围,如果n <= 100000,就不要写 O(n²) 的排序算法,改用快速排序的qsort或自己写归并排序。如果只是查找某个元素,先排序再二分查找,O(n log n) 比 O(n²) 好很多。遇到数组大小拿不准时,开到题目限制的 1.2 到 1.5 倍,既能防越界又不会白白浪费内存。死循环就靠调试定位:在循环体内加一行输出看它是否无限打印,检查循环条件的变量有没有被更新。
4.5 RE:数组越界、野指针和递归没有出口
现象:代码在本地随机崩溃,有时能跑有时不能,提交到 OJ 直接 RE。返回的日志只有一句“运行时错误”,没有具体堆栈。
原因:最常见的三种:数组下标越界访问,比如长度为 n 的数组写到了arr[n];指针没有初始化就直接使用,或者在指针释放后继续访问;递归函数缺少终止条件或终止条件永远不满足,导致栈溢出。这些错误在本地运行时不一定每次都崩,因为越界访问可能恰好落在未使用的内存区域,看起来“正常”,但在判题机的环境下,内存布局不同,就会触发访问非法地址的异常。
解决:数组相关代码,把所有下标都检查一遍,确保都落在[0, n-1]闭区间内。指针使用前判断是否为NULL。递归函数写完后先画一遍调用关系,找到递归的出口条件。另外,大数组不要定义在函数内部作为局部变量,局部变量在栈上分配,空间有限,改成全局变量或static变量更安全,这也是 RE 转 AC 的常用一招。
5. 比刷题更重要的是验证:三招把题库用到产生复利
5.1 对拍脚本:不再靠眼睛检查结果对不对
一道题写完,手输几组数据验证一下可能不够。在 Linux 或 Mac 环境下,我用一个小脚本实现自动对拍:同时运行你的程序和参考答案,用同一份随机生成的输入喂给它们,然后对比输出。只要有一次输出不一致,就把那组输入打印出来。
#!/bin/bash while true; do python3 gen.py > input.txt ./my_solution < input.txt > out1.txt ./standard_solution < input.txt > out2.txt diff out1.txt out2.txt > /dev/null if [ $? -ne 0 ]; then echo "差异出现在以下输入:" cat input.txt break fi done逻辑说明:gen.py是随机数据生成器,按题目约束生成不同规模的输入;my_solution是你写的程序,standard_solution是题解或你自己确认正确的暴搜版本。diff命令比较两个输出文件,返回非零表示有差异。
参数说明:如果你在 Windows 上,可以用批处理文件代替 bash,核心逻辑不变。gen.py的随机范围要覆盖边界,比如最小值、最大值、最大数量级都生成一遍,这样对拍才有意义。对拍脚本的价值不是找语法错误,而是揪出“你觉得对但实际不对”的隐藏逻辑问题。
5.2 先看数据范围再动笔,永远是最省时间的一步
我见过很多人在读题时跳过数据范围,直接开写,写到一半发现int不够用,或者算法复杂度严重超标,只能推倒重来。正确顺序是:先看输入输出的数据类型和取值范围,再确定用int还是long long,然后根据数据量级选算法,最后才动笔写代码。数据量在 1000 以内可以随意写,10000 就要注意常数优化,10 万以上必须用 O(n log n) 级别的算法。这套题库里中等题和综合题的数据范围都是有讲究的,不会让你白算复杂度。
5.3 每个 RE 和 TLE 都记一行,比收藏十个模板更值钱
最后一招更偏习惯:建立一个错题本,每次遇到 RE、TLE、WA 后不急着改代码,先记两行——题目编号、错误状态码,以及你定位到的根因。这样过一个学期,你翻开错题本会发现,来来回回犯的错就那么几类:边界没处理、格式错误、数组越界、复杂度问题。从那以后,我每刷完一个训练模块,都强制重做一遍前一个模块的错题,再碰新题目。这个习惯帮我少走了很多弯路,题库也从一个学期的“限时任务”变成了反复咀嚼的底层能力。希望帮到你。
本文还有配套的精品资源,点击获取