每年三月初,兰大计算机复试群里的气氛就开始紧张起来了,问得最多的一句话永远是“机试到底考什么?有没有往年题可以刷”。我当年准备2025年兰州大学计算机考研复试机试的时候,也被这个问题折腾过,前后翻了不少经验帖,踩了不少坑,最后才摸索出一套比较高效的准备思路。这篇文章我会把兰大复试机试的真题方向、核心考点、解题思路和AC代码全部整理出来,重点覆盖模拟排序、栈与表达式、搜索图论、动态规划这几类高频题型,每一道题都给出可以直接跑的完整代码,并讲清楚代码背后为什么要这么写。无论你是第一次接触考研机试的小白,还是已经刷过一些题但想针对性冲刺的选手,这篇内容都能帮你少走弯路。
需要先说明一点:为了尽量还原真实考场情况,我选取的题目均按历年考生回忆和公开讨论整理的真题风格进行还原,重点在于知识点、难度和控制方式与原题保持一致。机试这种东西,题型变来变去,考的核心能力永远是那几样,搞透套路远比追所谓“原题”重要。
1. 兰大2025复试机试:这些事实决定了你要怎么准备
1.1 机试平台、比赛规则与语言选择
兰大计算机考研复试的机试一般在校内OJ平台上进行,系统跟很多高校一样用的是HUSTOJ这类开源在线评测系统,提交后实时返回结果。考试时间通常是两个小时,题量在4到5道左右,难度从签到题到压轴题逐步递增。
语言方面,C++依然是最稳妥的选择,部分年份也支持Java和Python,但我的建议是除非你C++实在写不顺手,否则不要用Python搏机试。原因是Python在大数据量输入时容易超时,有些递归题会爆栈,而C++在OJ环境下有最稳定的编译支持和最多的参考代码。编译标准一般支持到C++17,但我比赛时习惯只写C++11能编译的代码,兼容性最好。
考试过程中不允许查阅纸质资料,也没有场内答疑。这意味着你对sort、map、queue这些STL容器的常用操作必须熟到条件反射,不能在考场上现场查用法。
1.2 命题风格与考核重点分析
从近几年的命题风格看,兰大机试非常看重基础数据结构的灵活运用,直接考“背诵型算法”的题目不多,更多是把经典算法包装成实际场景题。比如给你一个任务调度场景,让你用优先队列模拟;给你一堆日志记录,让你统计单词频率然后排序输出。
高频知识点基本可以圈定在四个方向:
- 模拟与排序:字符串处理、结构体排序、日期处理,这类题一般放在第一题或第二题,属于保分题。
- 线性数据结构:栈、队列、链表,尤其栈与表达式求值的组合考察频率很高。
- 搜索与图论:BFS求最短路、DFS连通块、图的遍历,偶尔配合曼哈顿距离或简单状态。
- 动态规划:最长上升子序列、背包问题、最长公共子序列,通常作为最后一题拉开差距。
一个很重要的判断是,兰大的机试难度并不追求竞赛级别,更接近“408数据结构的代码题+一点ACM基础入门题”的混合体。你不需要掌握后缀自动机、网络流、平衡树这些高级货,但必须把常见模板写得很熟,能够在二十分钟内从思路到代码一气呵成。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 四类常考题型拆解与解题思路
2.1 模拟与排序题
模拟题是兰大机试的固定开场,考察的是你对问题描述的提取能力和代码实现的细致程度。这类题通常不涉及高深算法,但非常考验边界条件处理,一旦漏掉一个隐藏条件就容易WA。
排序题最常见的是结构体多关键字排序。比如给一批学生记录,先按总分降序,总分相同按姓名字典序升序,再相同按学号升序。这道题的解题核心是写对cmp比较函数,并且理解C++中sort对比较函数的要求:必须提供严格的弱序,也就是对任意两个元素a和b,cmp(a,b)和cmp(b,a)不能同时为真。
模拟题的另一个高发考点是字符串处理,像单词统计、子串替换、格式转换。这类题目建议先把每个字母、空格、换行的情况都列清楚,再动手写代码,否则很容易在细节上翻车。
2.2 线性数据结构题
栈、队列、优先队列是兰大机试的常客。队列在BFS中会用到,优先队列在贪心类题目中很常见,而栈最经典的考点就是表达式求值。
表达式求值在机试中出现频率极高,而且变化多端。最简单的版本是只有加减乘除没有括号,进阶版本是加入括号甚至负数。核心思路有两种:一是利用两个栈(操作数栈和运算符栈)进行中缀表达式求值;二是先把中缀转后缀,再用后缀表达式计算。我推荐掌握第一种,因为代码更短,逻辑更好调试。
具体做法是:遍历字符串时,遇到数字就拆出完整数字;遇到运算符时,如果当前运算符优先级不高于栈顶运算符,就先弹出栈顶运算符计算,再把当前运算符压栈。这个“压栈前先算一把”的思想,本质上就是保证在任何时刻栈内的运算符优先级从底到顶是递增的。理解了这一点,代码就能写得流畅。
2.3 搜索与图论题
搜索题是复试机试中区分度最大的一类。常见题目包括迷宫最短路径、连通块个数、岛屿问题、地图染色等,基本上是BFS和DFS的变体。
解题之前一定要先分析数据规模,再决定用哪种搜索。数据量小(如50×50)可以放心DFS,代码简洁;数据量大(如500×500)且有最短路需求,建议使用BFS,因为BFS天然保证第一次到达终点就是最短步数,而DFS需要反复比较更新。对于连通块计数问题,DFS和BFS都能做,但要注意递归深度,必要时用栈模拟递归或直接改用BFS。
这里分享一个很多新手容易犯的错:在BFS入队后忘记标记已访问,导致同一个节点反复入队,轻则超时,重则死循环。正确做法是在push进队列的同时就把状态标记好,而不能等到出队时再标记。
2.4 动态规划题
动态规划在兰大机试里通常作为压轴题出现,难度明显高于前三类。常考的模型非常集中,主要是最长上升子序列(LIS)、最长公共子序列(LCS)、0/1背包和区间DP。
很多同学看到DP就发怵,其实复试机试的DP题通常有二十分钟内可以解决的套路。拿到题目先想两件事:状态代表什么,转移方程是什么。以最长上升子序列为例,设dp[i]表示以第i个数字结尾的最长上升子序列长度,那么dp[i]可以由所有满足j < i且a[j] < a[i]的dp[j]转移而来。状态定义清楚后,转移就是一层循环的事。
如果题目再加一个要求,比如输出最长上升子序列的个数,只需要额外开一个计数数组,在转移时同步累加方案数。这比直接做难题更贴近兰大机试的压轴题风格,属于“经典模型加一点小变化”,而不是推一个全新状态。
3. 真题还原:四道题的完整AC代码与实现细节
下面这四道题,是我按照兰大复试机试的常考方向整理出的代表题型。题目难度、数据范围和考察方式都参照近年真题风格设计,你完全可以把它们当成真题来刷。
3.1 成绩排序:模拟题的完整解法
题目描述:输入两个整数N、M,表示学生总数和需要输出的名次范围。接下来N行,每行包含学生姓名(不含空格)和成绩。排序规则为:总分从高到低,若成绩相同则按姓名升序排列。输出前M名学生的姓名和成绩。
输入样例:
text复制5 3
Alice 90
Bob 85
Cindy 90
David 80
Eve 85
输出样例:
text复制Alice 90
Cindy 90
Bob 85
Eve 85
AC代码:
cpp复制#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
struct Student {
string name;
int score;
};
bool cmp(const Student& a, const Student& b) {
if (a.score != b.score) return a.score > b.score;
return a.name < b.name;
}
int main() {
int n, m;
while (scanf("%d%d", &n, &m) != EOF) {
vector<Student> stu(n);
for (int i = 0; i < n; i++) {
cin >> stu[i].name >> stu[i].score;
}
sort(stu.begin(), stu.end(), cmp);
int outCnt = min(n, m);
for (int i = 0; i < outCnt; i++) {
printf("%s %d\n", stu[i].name.c_str(), stu[i].score);
}
}
return 0;
}
代码里最值得注意的有两个地方。一是cmp函数必须严格区分大于和小于,不能写成return a.score >= b.score,因为sort需要严格弱序,否则在部分编译器下会不稳定甚至异常。二是while (scanf(...) != EOF)这个写法,它保证了题目如果有多组输入,代码也能正确读取,这是机试中一个非常实用的技巧。
3.2 四则运算表达式求值:栈的灵活运用
题目描述:输入一个只包含非负整数和+、-、*、/四种运算符的字符串,计算并输出表达式的值。除法为整除,保证中间结果不会溢出。
输入样例:
text复制1+2*3-4/2
输出样例:
text复制5
AC代码:
cpp复制#include <iostream>
#include <string>
#include <stack>
using namespace std;
int main() {
string s;
while (cin >> s) {
stack<int> st;
long long cur = 0;
long long ans = 0;
char pre = '+';
int len = s.size();
for (int i = 0; i < len; i++) {
if (s[i] >= '0' && s[i] <= '9') {
cur = cur * 10 + (s[i] - '0');
}
if ((s[i] < '0' || s[i] > '9') || i == len - 1) {
if (pre == '+') st.push((int)cur);
else if (pre == '-') st.push(-(int)cur);
else if (pre == '*') st.top() *= (int)cur;
else if (pre == '/') st.top() /= (int)cur;
cur = 0;
pre = s[i];
}
}
while (!st.empty()) {
ans += st.top();
st.pop();
}
cout << ans << endl;
}
return 0;
}
这个解法用了一个很巧妙的思路:遍历字符串时,数字串依次还原成整数cur,但每次遇到运算符并不立即计算,而是保存为pre,等到读取下一个运算符时再决定前一个数字如何入栈。
*和/会直接修改栈顶元素,+和-会把数字压栈(减号压入负数),最后统一求和。这样处理的好处是不需要复杂的优先级判断,因为乘除法已经提前完成,栈里剩下的数字之间只有加法关系。注意len - 1这个条件,它的作用是确保表达式末尾的最后一个数字不会被漏掉,这是新手最容易漏的细节。
如果题目升级为带括号的表达式,这个单栈方法就不够用了,需要引入运算符优先级栈或转后缀表达式。不过以兰大机试的难度,掌握单栈法基本够应付。
3.3 岛屿数量:BFS/DFS连通块计数
题目描述:给定一个N行M列的01矩阵,其中1代表陆地,0代表水域。相邻的1(上下左右四个方向)属于同一个连通块。请输出矩阵中总共有多少个独立的陆地连通块。输入包含多组测试数据,每组第一行为N和M,之后N行每行M个整数。
输入样例:
text复制4 5
1 1 0 0 0
1 0 0 1 1
0 0 0 0 0
0 1 1 0 0
输出样例:
text复制3
AC代码:
cpp复制#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int MAXN = 505;
int g[MAXN][MAXN];
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int n, m;
void bfs(int x, int y) {
queue<pair<int, int> > q;
q.push(make_pair(x, y));
g[x][y] = 0;
while (!q.empty()) {
int cx = q.front().first;
int cy = q.front().second;
q.pop();
for (int k = 0; k < 4; k++) {
int nx = cx + dx[k];
int ny = cy + dy[k];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (g[nx][ny] == 1) {
g[nx][ny] = 0;
q.push(make_pair(nx, ny));
}
}
}
}
int main() {
while (cin >> n >> m) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> g[i][j];
}
}
int cnt = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (g[i][j] == 1) {
cnt++;
bfs(i, j);
}
}
}
cout << cnt << endl;
}
return 0;
}
这道题在剑指Offer和各大厂笔试题里都有类似版本,所以刷过真题的人应该不陌生。核心思路是遇到一个“1”就把答案加一,然后从这个点出发,把整个连通块里的所有“1”全部标记成“0”,这样之后就不会再重复计数。
我特意选择了BFS而不是DFS来实现,主要考虑是数据规模到500×500时,DFS递归深度可能接近25万层,容易爆栈。虽然很多OJ把栈空间调得很大,但在考试环境下没必要冒这个风险。BFS用队列实现,空间占用和DFS同数量级,但没有递归栈溢出的隐患。
3.4 最长上升子序列的个数:一维DP的进阶
题目描述:输入一个整数N,表示数组长度,接下来一行包含N个整数。输出最长上升子序列的长度,以及长度为该值的最长上升子序列的个数。这里定义子序列为原序列中不改变相对顺序的若干元素,且要求严格递增。如果存在多个不同的下标组合,视为不同方案。
输入样例:
text复制7
1 3 5 4 7
输出样例:
text复制4 2
AC代码:
cpp复制#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
while (cin >> n) {
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
vector<int> dp(n, 1);
vector<long long> cnt(n, 1);
int bestLen = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (a[j] < a[i]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
cnt[i] = cnt[j];
} else if (dp[j] + 1 == dp[i]) {
cnt[i] += cnt[j];
}
}
}
bestLen = max(bestLen, dp[i]);
}
long long ways = 0;
for (int i = 0; i < n; i++) {
if (dp[i] == bestLen) {
ways += cnt[i];
}
}
cout << bestLen << " " << ways << endl;
}
return 0;
}
这道题的经典基础版是只求最长上升子序列长度,加入方案数后就变成了“经典模型加一点变化”的典型考法。dp[i]表示以第i个元素结尾的最长上升子序列长度,cnt[i]表示以第i个元素结尾且长度为dp[i]的子序列个数。
转移的时候分两种情况:如果dp[j] + 1 > dp[i],说明找到了更长的子序列,此时直接继承cnt[j];如果dp[j] + 1 == dp[i],说明又找到了一条长度相同的路径,需要把cnt[j]累加进来。最后统计所有长度为bestLen的cnt[i]之和就是答案。这种“长度数组加方案数数组”的双DP套路,在兰大机试和很多大厂笔试中出镜率很高,值得专门练熟。同类的兄弟题目还有最长公共子序列,思路类似,一旦理解了状态转移的核心,刷起来会快很多。
4. 机试现场最容易踩的坑:从输入输出到编译环境
4.1 输入输出格式与多组数据的坑
机试WA(Wrong Answer)的原因里,输入输出格式错误占的比例远比想象中高。最典型的坑是题目要求多组输入,但代码只读一组就返回了。我见过太多同学在本地IDE测试样例通过,提交后却迎面一个大红WA,最后发现是输入循环没写对。
遇到“多组测试数据”这种字眼,最通用的写法就是while(cin >> n)或while(scanf(...) != EOF)。如果题目第一行给定了测试组数T,那就是for(int i = 0; i < T; i++),两种情况一定先分清楚再动手。
输出格式方面有几个细节也需要注意。一是行尾不能有多余空格,尤其是输出数组或序列时,很多人习惯在末尾也输出一个空格,这在严格判题的系统里直接算错。二是使用printf输出string时必须写成printf("%s\n", str.c_str()),直接用printf("%s", str)会导致编译错误或者输出乱码。三是输出浮点数时注意题目给的小数位数要求,保留几位就保留几位,不要自作聪明多输出。
4.2 边界条件、递归深度与数据溢出
边界条件是机试的第二大杀手。数组下标越界在本地可能不报错,提交后却可能RE(Runtime Error)或WA。每次写完循环,要习惯性检查:边界取没取到、负数下标有没有可能、数组大小是否刚好够。
数据溢出也是容易被忽视的问题。题目说“结果不超过int范围”,但中间结果很可能超,尤其是做乘法运算时,两个大数相乘直接撑爆int。我一般遇到有乘法、累加、方案数统计的情况,直接无脑用long long,反正内存不会差那几MB。方案数统计类的DP题更是如此,int存不下随时可能WA。
递归深度问题在DFS题里尤其突出。如果你发现DFS递归2000层就栈溢出了,要么换BFS,要么手动用栈模拟递归。不过考试时间有限,我建议优先考虑BFS,代码写起来虽然比DFS稍微复杂一点,但能从根本上避免递归深度带来的不确定性。
4.3 编译环境的兼容性细节
机试环境不一定和你本地的IDE完全一致。有些同学用新版本编译器写习惯了,代码里用了C++17的语法,结果考试环境只支持C++11,导致CE(Compilation Error)。我自己的习惯是:除了bits/stdc++.h这种万能头文件外,尽量按C++11标准写代码,并且每次提交前都确认一下编译器的版本和编译参数。
关于bits/stdc++.h,虽然很多OJ支持,但个别学校的评测系统可能不支持这个头文件。为了保险,我建议手工包含需要的头文件,比如<iostream>、<vector>、<string>、<algorithm>等。多写几行头文件换来的稳定性,值得。
还有一个很容易被忽略的坑:代码里不能写system("pause")、getch()这类交互式暂停语句。本地跑没问题,提交到OJ后程序会永远卡在等待输入的地方,最后TLE。上考场前把代码里的调试输出、暂停语句全部检查一遍,应该成为肌肉记忆。
5. 从真题倒推的30天备考计划
5.1 前三周:算法模板、STL和真题训练
很多同学复习机试有一个误区,就是觉得算法看了就会,代码敲不敲无所谓。机试是唯一一个“光看不练等于白看”的复试环节,必须每天都保持写代码的手感。我当时给自己定的计划是每天至少两小时,完全按真题风格来。
第一周的重点是STL容器和常用函数。vector、map、set、stack、queue、priority_queue、sort、reverse、unique这些必须熟练,能做到“想到就能写出来”的程度。可以通过跑一些经典题目来练习:用map做单词统计、用priority_queue做合并果子、用stack做括号匹配。这一周的目标不是刷难题,而是把工具练熟。
第二周重点刷数据结构和基础算法题。树的前序中序后序遍历、图的邻接表与邻接矩阵、并查集、最小生成树、Dijkstra、DFS、BFS,这些是兰大机试的核心题库。刷题平台首选洛谷的普及/提高组题目,其次是POJ的经典200题。如果目标是复试而不是ACM竞赛,不建议死磕难题,每天两三道中等题足够。
第三周开始进入真题和模拟题阶段。你可以把历年408数据结构代码题翻出来重做一遍,再把能找到的机试回忆题、各大院校复试机试题、蓝桥杯真题中的基础题拿来练手。很多学校之间的机试风格非常相似,比如你刷了苏大的机试题再去看浙软的机试题,会发现考点重合度很高。兰大、苏大、浙软这类学校的上机题,思路和难度并没有本质差别,完全可以互相作为模拟素材。我会把多校机试题交叉着刷,也是为了提升对不同题风的适应性。
5.2 最后一周:全真模拟与查漏补缺
最后一周不建议再大规模刷新题,而是要进入“模拟考试状态”。我建议找两三套完整的机试题目,扎扎实实用两小时套时间做一遍,中间不看手机、不翻资料、不查语法手册,完全模拟真实考场环境。
模拟的重点是时间分配。真实机试中,前两题通常比较简单,尽量在三十分钟内拿满;第三题是中等难度,需要四十分钟左右;最后一题DP如果十五分钟内没有思路,果断放弃去检查前面的代码,比死磕压轴题划算得多。机试看的是AC题数和罚时,放弃一题保住三题,往往比四题都做一半拿不到分要强。
模拟结束后的复盘比做题本身更重要。把每道题的错因分类整理:是思路问题、边界问题、还是语法问题?我当时做了个简单的表格,记录每道题的失分点和对应解法,考前一天重点看这些记录,比临时再看一遍算法模板更有用。
6. 常见问题速查表
6.1 高频问题与解决建议
这里整理了机试准备过程中大家问得最多的问题,也是我备考时的真实困惑,直接给结论:
| 问题 | 解决建议 |
|---|---|
| 没有OJ经验,第一次接触在线评测怎么办 | 先去洛谷注册账号,从入门题开始,熟悉提交、判题、看错误的完整流程 |
| 用C++还是Java还是Python | 首选C++,稳定性最好,参考代码最多,STL效率高 |
| 机试会不会考操作系统、计算机网络知识 | 不会,机试只考代码能力和算法,笔试环节才考专业课 |
| 题目做一半没AC,能拿部分分吗 | 通常按测试点判分,没AC的题也可能过部分数据点,所以不要留空 |
| 平时刷题用什么平台 | 洛谷、蓝桥杯题库、POJ、HDU,优先选兰大复试相关回忆题和408真题改编题 |
| 需要学多深的算法 | 能熟练掌握栈、队列、树、图、排序、搜索、基础DP就足够,竞赛算法不需要 |
关于“没AC的题能不能拿部分分”这个问题再展开说一下。大多数OJ按测试点给分,如果你代码能过前几个简单样例,就能拿到对应分数。所以考试时就算没有完整思路,也可以先写个暴力版本,至少能拿部分点。别觉得丢人,在复试这种一分定胜负的场景里,能拿一分是一分。
6.2 关于复试机试的几点个人建议
第一,尽早开始准备,不要等初试成绩出来再启动。机试能力的提升是一个缓慢积累的过程,临时抱佛脚的效率极低。我当时就是从初试考完就开始每天写题,前前后后刷了两百多道题,到了复试前已经形成肌肉记忆了。
第二,不要追求难题偏题,回归基础才是王道。兰大机试的风格决定了基础题占比很高,有些年份甚至只靠基础题就能拉开差距。把排序、栈、队列、BFS、DFS、基础DP这些吃透,比勉强去理解一个AC自动机有意义得多。
第三,机试不仅是做题,还是一次心态测试。真实考场上总会出现意料之外的情况,比如样例过了一大半却卡在一个奇怪的边界,或者编译环境和自己电脑不一样。这时候最忌讳的是慌,记住一个原则:遇到问题先读题,再读代码,最后再考虑重写。很多WA其实只是很小的一行代码错误。
我最后再分享一个自己的习惯:每次模拟考试后,我会把出错的题目截图保存到手机相册,睡前翻一遍。看得多了,那些容易踩的坑会自动刻进脑子里。这个习惯帮我避免了很多次同样的错误,希望对你也有用。
