每年CSP-S初赛结束,阅读程序部分都是讨论热度最高的区域,而2021年提高级的阅读程序第3题,更是当时公认的“分水岭”题目。很多同学考完对完答案的第一反应不是“这题我不会做”,而是“代码我好像看懂了,但选项完全不知道怎么推”。这道题把指针、多维数组、函数指针、递归回溯四个知识点全部揉进一个迷宫搜索程序里,表面看是一道“读程序”题,实际上考的是你能不能像一个C++编译器一样,把一段代码“翻译”成明确的执行轨迹。
这篇文章会从命题逻辑、核心代码结构拆解、考点原理、手动推导方法、考场应对策略五个方向,把这题讲透。如果你正在备考CSP-S或CSP-J,或者你的C++已经学到了指针和递归但总觉得读代码费劲,这篇文章就是给你准备的。题目原版较长的函数名和变量名,我会用更直白的方式还原它的核心结构,帮助你把精力放在“读代码的方法”上。
1. 这道题到底在考什么:先从真题的“外在气质”说起
1.1 压轴题为什么选它:考点分布与命题逻辑
2021年CSP-S提高组初赛的阅读程序部分整体难度比往年略高,但前两题都还算“常规”:一道是简单的数学递推,一道是字符串处理。到了第3题,命题组明显换了思路——代码篇幅不算特别长,但每一行都踩在C++学习的痛点上。
这类“迷宫搜索+方向策略”的题目,之所以频繁出现在阅读程序压轴位置,是因为它一次性覆盖了四个层面的能力:第一,你要知道二维数组作为参数传递时发生了什么;第二,你要看得懂函数指针这种“间接调用”的写法;第三,你要理解递归DFS在搜索路径时,访问标记是如何回溯的;第四,你还要能在脑子里模拟出不同方向选择顺序对结果的影响。
换句话说,这道题不是单纯考“你会不会写DFS”,而是考“你会不会读一个被包装过的DFS”。很多选手平时写搜索都依赖模板,一旦代码里出现了int *p = ...、(*op[step])(...)这种语法包装,就容易被绕晕。这恰恰是命题组想看到的结果:区分出真正理解程序执行逻辑的人,和只会套模板的人。
1.2 真题核心代码结构还原(可运行示范版)
由于2021年CSP-S初赛真题网上流传的版本较多,不同抄录版的变量名和细节略有差异,我这里不逐字复刻官方代码,而是把这道题最核心的逻辑用一段等价示范代码还原出来。这段代码保留了原题全部关键设计:二维网格、起点终点、障碍物、两组方向选择策略、递归回溯、步数统计。读懂这段结构,再回去看真题时会轻松很多。
cpp复制#include <cstdio>
#include <cstring>
using namespace std;
const int N = 15;
int n, m, sx, sy, tx, ty;
char mp[N][N];
int vis[N][N];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
// 两个策略,决定每次优先尝试哪个方向
int seq[2][8] = {
{0, 1, 2, 3, 0, 2, 1, 3},
{3, 2, 1, 0, 1, 3, 0, 2}
};
int minIdx(int a, int b) { return a < b ? a : b; }
int maxIdx(int a, int b) { return a > b ? a : b; }
// 函数指针数组:op[0] 取向较小值,op[1] 取向较大值
int (*op[2])(int, int) = {minIdx, maxIdx};
int answer[2];
int findPath(int x, int y, int cur, int cnt) {
if (x == tx && y == ty) {
answer[cur] = cnt;
return 1;
}
int *p = seq[cur];
int start = op[cur](0, 3); // 根据策略确定第一个方向
for (int t = 0; t < 4; t++) {
int k = seq[cur][(start + t) % 4];
int nx = x + dx[k];
