简介:西南科技大学OJ代码合集是一份面向算法学习者与编程竞赛选手的题目解答资源,覆盖数据结构、图论、搜索、动态规划等计算机科学核心领域。压缩包共117个文件,以110个C++源文件为主体,每个文件对应一道题目的完整解法,另附4个readme、1个license及md说明等辅助文档,整体大小仅20KB,轻量化便于下载浏览。目前已有183人学习下载,适合正在备战校赛、蓝桥杯或提升编程能力的入门至进阶用户。内容精选哈夫曼译码、中缀表达式转后缀表达式、Prim算法求最小生成树、二叉排序树实现与查找、单链表信息分类等典型题目,代码经过多次测试与优化直至AC通过,并注重处理输入输出边界与执行时间限制。读者通过研读这些题解,不仅能理解算法设计思路,还能掌握代码性能优化、异常情况处理的实战技巧,也可作为日常刷题与期末复习的参考汇集。
1. 西南科技大学OJ代码合集:刷题人的第一份离线题库
如果你正在刷西南科技大学OJ,或者准备用它来练算法基础,这份「西南科技大学oj的代码合集.7z」值得你先下载再慢慢研究。它不是课件,也不是题目截图,而是一批已经通过评测的C/C++代码,覆盖了OJ上最常见的几类题型:输入输出格式题、简单模拟、字符串处理、排序、递归、素数判断、DFS/BFS 和入门级动态规划。换句话说,这份资源把 OJ 里最容易卡住新手的题目解法直接打包了,适合两类人:一是刚接触 OJ、连提交格式都搞不清的初学者,二是想快速对比思路、验证自己写法的刷题老手。
我拆这份压缩包的时候,发现里面代码风格差异挺大,毕竟不是一个人写的,这说明它更像一个「民间积累」,不是官方标准答案。但这不妨碍它有用——只要你抓得住关键,能读懂每段代码的思路,再碰到同类型的题,你完全可以自己改出能过的版本。下面我从解压、组织代码、按题型拆解到提交和排错,完整走一遍。
2. 把代码合集变成自己的武器库:解压、归类与文件筛选
拿到.7z压缩包,第一步不是急着看代码,而是先把文件结构搞清楚。OJ 代码合集这种资源,最怕的就是「解压一时爽,打开全是乱码文件名」,所以我会先做一次系统性的解压和归类。
2.1 解压 .7z 的正确姿势:工具选择与编码问题
Windows 用户直接用 7-Zip 解压就行,别用系统自带的「全部解压」,它对 .7z 的支持不完整,有时候会报错或者丢文件。macOS 用户可以用p7zip,命令行操作更可控:
# macOS 安装 p7zip brew install p7zip # 解压到指定目录,保留原始文件结构 7z x 西南科技大学oj的代码合集.7z -o./oj_codes这里x是解压并保留目录结构,-o指定输出目录。如果你只是想看看压缩包里有什么,可以先列清单:
7z l 西南科技大学oj的代码合集.7z列出清单这一步特别重要,我一般会先看文件名是全中文还是拼音缩写,这决定了后面怎么批量归类。如果解压后出现文件名乱码,常见原因是压缩包的编码格式是 GBK,而你的系统默认 UTF-8。这时候用 7-Zip 打开压缩包,右键选择「以UTF-8模式重新打开」,再解压,大部分乱码都能解决。
解压之后,你会看到一堆.cpp、.c文件,也可能有.txt格式的代码片段。这一步不要急着一个一个打开,先做个统计,看看文件总数和代码语言分布。
2.2 批量重命名与按题型归档:三步把散文件变成检索库
代码合集最大的痛点不是没有代码,而是「找不到对应题目的代码」。我见过很多人解压完就扔在桌面上,刷题的时候挨个双击打开找,效率极低。我的做法是,先批量提取每个文件的头几行,判断题目类型,再归档。
先看文件头部信息:
# 用 head 命令批量查看所有 .cpp 文件的前 5 行 for f in *.cpp; do echo "===== $f ====="; head -n 5 "$f"; done这一步能帮你快速判断哪些文件是同一类题,比如看到#include <stdio.h>加while(scanf(...) != EOF)的组合,基本是输入输出类;看到#include <iostream>和sort,是排序类。但文件多的时候命令行太啰嗦,我一般直接用 Python 脚本扫描关键词:
import os import shutil keywords = { 'sort': '排序', 'dfs': '搜索', 'bfs': '搜索', 'prime': '素数', 'strcmp': '字符串', 'memset': '模拟' } for fname in os.listdir('.'): if not fname.endswith('.cpp'): continue with open(fname, 'r', encoding='utf-8', errors='ignore') as fp: content = fp.read() for kw, category in keywords.items(): if kw in content: os.makedirs(category, exist_ok=True) shutil.copy(fname, category + '/' + fname) break这段脚本的逻辑是:先定义关键词和类别的映射,然后遍历当前目录下所有.cpp文件,读取内容,如果包含某个关键词,就复制到对应类别的文件夹里。errors='ignore'是处理编码问题的兜底方案,有些代码文件可能不是标准 UTF-8,直接读会报错,设置为忽略后读成乱码也没关系——我们只是做关键词匹配。
归档完成后,你的代码库就从「一堆散文件」变成了「按题型分层」的结构,找题的时候直接进对应文件夹翻。
提示:文件名如果是纯数字(比如
1000.cpp、1001.cpp),那大概率对应 OJ 题目编号,建议保留原始文件名,不要改成中文描述名,因为你自己改的名字可能和 OJ 题目对不上。
3. 从入门到进阶:C语言基础题与OJ输入输出格式解析
西南科大 OJ 的基础题占比不低,尤其是大一课程配套的题目,核心考点就三个:格式、边界、精度。格式错了,代码再对也 WA(Wrong Answer),所以这一章的节奏很关键,我按题型拆开讲。
3.1 多组输入直到 EOF:while(scanf) 结构的标准写法
OJ 上的很多题,不告诉你输入多少组数据,只说「输入包含多组测试数据,每组占一行」。新手最常见的错误是只读一组就输出,然后跑样例是对的,一提交就 WA。
标准做法是while (scanf(...) != EOF),这个结构几乎是所有 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(即 -1)。while循环会一直读下去,直到没有输入为止。参数说明:%d %d中间的空格表示匹配任意数量的空白符,包括空格、换行、Tab,所以输入1 2和1\n2都能正确处理。
这个模板还能扩展。如果输入结束条件不是 EOF,而是0 0这种特殊标志,写成:
while (scanf("%d %d", &a, &b) == 2 && (a || b)) { // 处理 a b }== 2判断是否成功读入两个数,(a || b)判断是否读到结束标志。注意顺序:先判断读取成功,再判断结束条件,不能反过来,否则 EOF 时 a、b 是未定义值,容易误判。
3.2 浮点数输出的精度陷阱:printf 格式化与四舍五入
OJ 对浮点数输出要求极度严格。常见要求是「保留两位小数」,你写printf("%.2f", x)没问题,但如果题目要求「四舍五入保留三位」,而你的计算过程有浮点误差,结果可能差 0.001 导致 WA。
我见过最典型的翻车案例:计算1.005保留两位小数,直接printf("%.2f", 1.005)在某些编译器下输出1.00,因为浮点数的二进制表示里,1.005 实际是1.00499999...。解决方式有两种,第一种是加一个极小量:
printf("%.2f\n", x + 1e-8);1e-8是一个比精度要求小得多的修正量,它能把1.004999...推到1.005000...,让舍入正确。第二种方式是先乘后除:
double y = (int)(x * 100 + 0.5) / 100.0; printf("%.2f\n", y);这行代码把x乘 100,加 0.5 后取整,再除以 100,手动完成四舍五入。参数说明:100是保留两位小数的放大倍数,如果保留三位改成 1000。但这个方法有个坑,x很大时会溢出,所以只适合数值范围小的场景。
注意:如果 OJ 题目要求「保留两位小数」且数据量很大,浮点运算加
1e-8是更稳妥的做法,不要用取整法,因为大数取整容易出精度问题。
3.3 字符与字符串输入:getchar 吃掉换行符的问题
OJ 字符串题几乎是 C 语言学习者的一道坎。题目形式通常是「输入一个整数 n,接下来 n 行每行一个字符串」。新手最容易在混合输入时翻车——先用scanf("%d")读数字,再用gets读字符串,结果第一行字符串读出来是空的。
原因是scanf("%d")读完数字后,回车键产生的换行符还留在输入缓冲区里,gets会先把这个换行符读走,导致第一行字符串丢失。解决方式有两种:
// 方式一:清掉缓冲区里的换行符 scanf("%d", &n); getchar(); // 吃掉换行符// 方式二:用 scanf 按格式串读,跳过空白符 for (int i = 0; i < n; i++) { scanf(" %s", str); }方式二里的" %s"前面加了一个空格,这个空格指示scanf跳过所有前导空白符(包括上一次遗留的换行),直接读下一个非空白字符。参数说明:%s会自动在末尾加\0,数组长度必须比字符串长度多 1,否则越界写入会破坏栈,OJ 上报 Runtime Error。
4. 数据结构的代码呈现:从简单模拟到排序与查找
数据结构类题目在 OJ 里通常不是要求手写红黑树,而是考查你有没有能力用数组模拟常见结构,以及排序和查找的边界处理。这份代码合集里,排序和模拟的题解数量相当可观,我把它们拆开说。
4.1 手写排序与 qsort:函数指针和比较器的边界
合集里排序相关代码通常有两种写法:手写冒泡/选择排序,或者调用qsort。新手阶段,手写一次冒泡有助于理解 O(n²) 的复杂度来源,但在 OJ 上数据量超过 1e4,冒泡就会超时(TLE)。
正确姿势是直接了解qsort的用法:
#include <stdlib.h> #include <stdio.h> int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } int main() { int arr[1005]; int 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]); return 0; }逻辑说明:cmp是比较器,qsort排序时会反复调用它。(*(int *)a - *(int *)b)返回负数、零、正数,分别表示 a 小于、等于、大于 b,所以结果是升序。如果想降序,把 a、b 换个位置。参数说明:qsort四个参数分别是数组首地址、元素个数、每个元素大小、比较函数指针。
这里有个边界坑:*(int *)a - *(int *)b如果两个数都是很大的 int,差值可能溢出,导致比较结果错误。稳妥写法是换成if判断:
int cmp(const void *a, const void *b) { int x = *(int *)a, y = *(int *)b; return (x > y) - (x < y); }(x > y) - (x < y)这个表达式是 C 语言里一种无溢出的比较写法,x 大于 y 时整个表达式返回 1,小于返回 -1,相等返回 0。
4.2 数组模拟栈和队列:front、rear 指针的取模逻辑
OJ 里很多「模拟题」不用真的实现链式结构,用数组加指针就够了。但指针的移动逻辑写错,就会陷入死循环或者越界访问。
一个典型的循环队列实现:
#define MAXN 1005 int queue[MAXN]; int front = 0, rear = 0; void push(int x) { queue[rear] = x; rear = (rear + 1) % MAXN; // 取模实现循环 } int pop() { int x = queue[front]; front = (front + 1) % MAXN; return x; } int empty() { return front == rear; }逻辑说明:rear指向下一个要插入的位置,front指向当前要弹出的位置。取模% MAXN让两个指针在数组范围内循环移动,避免数组越界。注意empty()判断front == rear,这是空队列的标志,但循环队列还有一个「满」的状态——如果允许填满,front == rear同时是空和满的标志,所以工程实现上通常会牺牲一个存储位,或者加一个count计数器。
OJ 题里一般不容易踩满队列的坑,因为题目设定的操作次数远小于数组容量,但如果你把MAXN设成恰好等于最大容量,而且 push 的数又多,就可能出现「队列满了但判断不出」的情况。我一般会把MAXN开成题目上限加 5,留出余量。
4.3 二分查找的边界:mid 的取值与死循环
排序之后大概率会配合二分查找出题。二分查找代码短,但边界条件写错,要么查不到,要么死循环。
int binary_search(int arr[], int n, int target) { int left = 0, right = n - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) return mid; else if (arr[mid] < target) left = mid + 1; else right = mid - 1; } return -1; }逻辑说明:left <= right表示区间非空才继续。mid = left + (right - left) / 2比(left + right) / 2更安全,因为left + right可能溢出,尤其当数组很大时。left = mid + 1和right = mid - 1的 +1/-1 是为了保证每次循环区间都在真正缩小,如果写成left = mid且恰好left + 1 == right时,mid 等于 left,最终会死循环。
这个死循环的坑,我在刷题时踩过不止一次,而且代码合集里的二分实现也有这种错误版本,我看到文件里写的是left = mid时,一眼就能判断这个代码没在真实数据上跑过。
5. 算法思维题拆解:递归、DFS/BFS 与入门动态规划
OJ 里真正拉开分数差距的,不是语法题,而是需要算法的题目。代码合集里递归和搜索类的代码质量参差不齐,但思路是可以学的。这一章我按「怎么读、怎么改、怎么自己写」三个层次拆解。
5.1 递归题的三个关键:终止条件、状态传递、回溯恢复
合集里最典型的递归题是汉诺塔和全排列。汉诺塔代码短,但递归栈的执行过程对新手来说像黑匣子。我会先看代码的终止条件是否在函数最前面,再看递归调用的参数是否在变化。
以全排列为例:
#include <bits/stdc++.h> using namespace std; int n; int a[15]; bool used[15]; void dfs(int idx) { if (idx == n) { for (int i = 0; i < n; i++) cout << a[i] << " "; cout << endl; return; } for (int i = 1; i <= n; i++) { if (used[i]) continue; used[i] = true; a[idx] = i; dfs(idx + 1); used[i] = false; // 回溯恢复 } } int main() { cin >> n; dfs(0); return 0; }逻辑说明:idx == n是终止条件,表示已经填满 n 个位置,输出结果。used[i]记录数字 i 是否用过,for循环枚举每个可用数字。used[i] = false是回溯的关键——递归返回后,要把当前数字释放掉,否则同一层后续的尝试会错误地跳过这个数字。
回溯恢复这一步,是我判断代码合集里某段递归代码是否可靠的核心标准。如果看到递归调用之后没有对称的「撤销操作」,这段代码大概率输出会有重复或遗漏。参数说明:a[15]和used[15]的 15 是题目给定的最大规模,如果 n 超过 13,全排列的运算量就已经超出常见 OJ 时限了。
5.2 DFS 与 BFS 的选型:最短路径用 BFS,可行解用 DFS
搜索题里,最关键的不是背模板,而是判断用 DFS 还是 BFS。我见过合集里有人拿 DFS 跑迷宫最短路,样例过了,一提交就 TLE,因为 DFS 会沿一条路走到黑,可能绕远路后才到终点,而 BFS 是按层扩展,第一次到终点就是最短路径。
BFS 的经典模板:
#include <bits/stdc++.h> using namespace std; int n, m; char maze[105][105]; bool vis[105][105]; int dx[] = {1, -1, 0, 0}; int dy[] = {0, 0, 1, -1}; struct Node { int x, y, step; }; int bfs(int sx, int sy, int ex, int ey) { queue<Node> q; q.push({sx, sy, 0}); vis[sx][sy] = true; while (!q.empty()) { Node cur = q.front(); q.pop(); if (cur.x == ex && cur.y == ey) return cur.step; for (int k = 0; k < 4; k++) { int nx = cur.x + dx[k]; int ny = cur.y + dy[k]; if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue; if (maze[nx][ny] == '#') continue; if (vis[nx][ny]) continue; vis[nx][ny] = true; q.push({nx, ny, cur.step + 1}); } } return -1; }逻辑说明:Node结构体存坐标和步数。vis数组标记访问过的位置,避免重复入队。四方向数组dx和dy的顺序不影响正确性,但建议固定顺序(比如上、下、左、右),方便调试。越界判断nx < 0 || nx >= n || ny < 0 || ny >= m写在最前面,防止数组越界访问。步数cur.step + 1是 BFS 的层级递增值,第一次到达终点时,这个值一定是最短步数。
这段代码我建议你抄下来,自己在本地跑几个迷宫样例,把vis数组打印出来,看看到底哪些位置被访问过,它能帮你理解 BFS 为什么效率高于 DFS——每个格子最多入队一次,时间复杂度是 O(n×m)。
5.3 动态规划入门的递推写法:以斐波那契和数塔为例
动态规划是 OJ 进阶的分水岭。合集里有不少递推代码,但很多写成了「看起来像 DP 的递归」,递归深度一深就爆栈。比如斐波那契,下面这种纯递归在 n=40 时已经慢得离谱:
int fib(int n) { if (n <= 2) return 1; return fib(n - 1) + fib(n - 2); }正确做法是递推:
long long fib[1005]; void init() { fib[1] = fib[2] = 1; for (int i = 3; i <= 1000; i++) { fib[i] = fib[i - 1] + fib[i - 2]; } }逻辑说明:fib[i]只依赖fib[i-1]和fib[i-2],从小到大依次计算,每个值只算一次,时间复杂度 O(n),空间复杂度 O(n)。参数说明:long long是必要的,斐波那契第 50 项就已经超过 int 范围。如果你只需要最后一项,还能节省空间写成滚动数组:
long long a = 1, b = 1, c; for (int i = 3; i <= n; i++) { c = a + b; a = b; b = c; }a、b分别保存前两项,c是当前项,一轮更新后a = b、b = c,把旧值丢掉。这种空间优化在多层 DP 里更常用,但基础阶段先把数组版本写对更重要。
数塔问题(数字三角形)也是 OJ 常客,核心是自底向上的递推,从倒数第二行开始,每格取下一行两个子节点中的较大值加上自身,状态转移方程是dp[i][j] += max(dp[i+1][j], dp[i+1][j+1])。代码合集里的数塔题解基本是这个套路,改一下数组边界就能跑。
6. 提交与排错避坑:西南科大OJ的常见问题、查重风险与代码规范
代码能跑不算完,能在 OJ 上 AC 才算完。我拆这份合集的时候,发现不少代码逻辑没问题,但提交时还是会 WA 或者 RE,原因集中在输入输出格式、数组越界和查重这三个方面。这一章集中写坑,每个都是血泪经验。
6.1 多组输入没处理干净:样例对但 WA 的经典现象
现象:本地跑样例完全正确,一提交就是 Wrong Answer。原因:题目要求「多组测试数据」,代码只读了一组就输出,或者读到了文件末尾但没有正确处理。解决:检查主函数入口处是否有循环读取。对比合集里的 AC 代码,几乎每一份都有while(scanf(...) != EOF)或while(cin >> x)的结构。如果你的代码只在main里写了一次读取,马上补上循环结构。
6.2 数组越界导致的 Runtime Error
现象:提交后直接报 Runtime Error,本地不管怎么跑都不崩溃。原因:OJ 的评测环境会启用内存保护,数组越界写入会触发段错误。很多新手开数组习惯int a[n],但题目数据量比你预估的大,比如题目说「n <= 1000」,你可能用 1000 当数组长度,而某些边界用例恰好用到下标 1000,越界 1 个单位,评测机上就崩了。解决:数组长度统一加 10 到 20 的余量。int a[1000]改成int a[1010],char s[1005]改成char s[1020],成本是几个字节的额外内存,但能省大量排查时间。
6.3 输出格式多了一个空格
现象:答案完全正确,但 Presentation Error 或者 WA。原因:OJ 严格对比输出结果,包括空格和换行。合集里有些代码直接printf("%d ", a[i])不带判断,最后一个数字后面多了空格,导致格式错误。解决:要么用循环加条件判断,要么把输出先拼到字符串里再统一输出:
for (int i = 0; i < n; i++) { if (i) printf(" "); printf("%d", a[i]); }if (i)的写法表示「不是第一个元素就先输出一个空格」,这样保证行尾没有多余空格,是 OJ 输出格式的通用技巧。
6.4 代码查重风险:直接复制合集等于把自己推上风口
现象:从合集里复制整段代码提交,结果被判定为抄袭或代码相似度过高。原因:OJ 的后台普遍使用代码查重系统,对提交的源码做相似度比对。合集里流传的代码,可能已经被几百人提交过,查重系统一看就知道是同源代码。解决:绝对不要原样提交合集里的代码。正确用法是读它的思路、看懂它的变量名和结构,然后自己重新实现——改变量名不算重新实现,要做的是换一种写法。比如人家用while循环你改成for循环,人家用数组你改用vector,逻辑等价但代码形态不同。这个避坑点可能是我整篇最强调的一条。
6.5 代码规范与注释习惯:给未来的自己留后路
合集里很多代码没有注释,变量名是a、b、t,看的时候费劲,改的时候更费劲。我的习惯是做题时就顺手写好注释,不是写给别人看,是写给三个月后的自己——那时候你可能完全忘了当时的思路。
// 状态:dp[i][j] 表示从顶部走到第 i 行第 j 列的最大和 // 转移:dp[i][j] = max(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j] // 边界:dp[0][0] = triangle[0][0]注释里的状态定义、转移方程、边界条件是动态规划题目的三要素,把它写清楚,等于把解题思路固化在代码里,下次同类题直接套用框架。从那以后我每次刷题都强制自己走一遍「先写思路注释、再写代码、最后补边界测试」的流程,这份代码合集的正确打开方式也是一样——它不是答案,是素材,你从素材里提炼思路,重新实现成自己的代码,才真正把题做透了。
希望这份拆解能帮你在西南科大OJ 上少走弯路,把合集里的代码转化成你自己的题库弹药。
本文还有配套的精品资源,点击获取