1. 为什么第一天的第一题选了P1101 单词方阵如果今天是你的蓝桥杯备赛第一天,我建议你先把 P1101 单词方阵 这道题做透。原因很简单:它不是难题,却把搜索类题目里最容易被忽略的细节全暴露出来了——方向怎么枚举、边界怎么判、标记怎么打。我第一次做这道题时,大概花了三十分钟;回头看,真正有价值的不是提交通过那一刻,而是把“为什么这样写”想清楚的过程。这篇就按我 Day1 的实际备赛思路,从题目拆解、方向枚举、完整代码到踩坑复盘,完整过一遍。1.1 这道题在蓝桥杯备赛里的定位蓝桥杯省赛的题目大致分几类:填空、模拟、搜索、图论、动态规划。P1101 这种“字符矩阵里找单词”的题,属于搜索题里的入门精品。它不涉及复杂的剪枝,也不需要状态压缩或记忆化,但能把二维数组遍历、方向数组、边界判断一次练熟。备赛第一天选它,就像训练营开营先练站姿,后面跑动、对抗才有基础。很多同学觉得搜索题难,是因为一上来就碰迷宫最短路径、连通块数量,这些题既要用 DFS 又要处理访问标记和回溯,很容易劝退。但 P1101 把搜索题的骨架单独拎出来:你只需要从起点出发,沿着固定的方向,一步步核对字符是否匹配,全程没有任何决策分支。这种“低难度但高信息量”的题目,恰好适合做备赛第一天的热手题。1.2 读完题之后的第一反应:别急着写 DFS看到“单词方阵”四个字,多数人的第一反应是写 DFS 回溯:从某个字母出发,上下左右搜索,尝试拼出目标单词。但我必须泼一盆冷水:这道题用真正的 DFS,反而容易把自己绕晕。题目里的隐藏单词是yizhong,它在方阵中只能沿着八个方向之一直线放置。什么叫直线放置?就是从起点开始,一直往同一个方向走,中间不拐弯,连续拼出 y-i-z-h-o-n-g 这七个字母。换句话说,这条路径在起点和方向决定后就已经完全确定,不存在“下一步有多个选择”的情况。DFS 回溯的价值在于处理“多分支、可回退”的状态空间,而这里根本没有分支,硬上回溯只会让你在方向判断上做大量无用功。所以正确的心理建设是:这道题真正要练的是“方向枚举 + 字符串比对”。把八个方向看成八条可能的路径,每个方向都试一遍,能匹配就打个标记。想明白这一点,代码量瞬间少一半。1.3 样例输入里藏着的关键信息题目输入第一行是方阵边长 n,后面跟着 n 行字母。输出是一个同样大小的方阵:如果某个字母属于某个完整的yizhong单词,就原样输出;否则输出星号*。这里有一个容易被忽略的细节:同一块字母可能被多条匹配路径共用。比如两个单词从不同起点出发,在中途某几个格子上重叠,这些格子仍然应该保留。因为你最终判断的是“这个格子是否属于任意一条完整路径”,而不是“这个格子是否只属于一条路径”。这也决定了解法里要单独开一个标记数组,而不是在匹配失败时把标记撤销。另一个细节是方向一致性:从起点到终点,七个字母必须严格朝同一方向排列。有的同学写检查函数时,每走一步都重新枚举方向,结果把直线路径走成了折线路径,这种错误在样例上经常看不出来,但一提交就错。2. P1101 题目到底在说什么:输入、输出与规则拆解2.1 字母方阵的规则与隐藏单词P1101 的题干用一句话概括是:给你一个 n×n 的字母方阵,方阵里可能藏着若干个单词yizhong,它们可以横着、竖着、斜着排列,但每条单词必须是一条直线。你需要做的,是把所有属于这些单词的字母保留下来,把不相关的字母改成星号。n 的范围在常见评测数据里一般不超过 100,所以最暴力的复杂度也是 O(n² × 8 × 7),也就是最多几十万次字符比较,在任何语言里都能轻松通过。理解这一点很重要:如果你在写这道题时还想各种剪枝优化,那就是把一门心思都花错了地方。蓝桥杯省赛里很多题的时间限制其实很宽松,关键不是把程序跑得多快,而是把规则理解得足够准确。为了让你真正吃透规则,我随手设计一个最小可验证场景。假设 n=7,方阵第一行正好放了一个横向的yizhong,主对角线上又放了一个斜向的yizhong,它俩在y这个起点上重叠。正确输出里,第一行和对角线上的字母都应该被保留,其他格子全是*。如果你写的程序总是漏掉斜向或重叠的字母,多半就是规则理解出了问题。2.2 八方向增量表是整道题的灵魂二维方阵中,从某个格子 (x, y) 出发,可以走八个方向。为了在代码里统一处理,一般会用方向增量数组表示:水平向右:(1, 0)水平向左:(-1, 0)垂直向下:(0, 1)垂直向上:(0, -1)右下方:(1, 1)左上方:(-1, -1)右上方:(1, -1)左下方:(-1, 1)这里使用的坐标约定是 x 表示行下标、y 表示列下标。不同的编程习惯可能把行和列反过来,但只要你始终保持“同一套坐标体系”,结果都一样。为什么方向增量数组这么重要?因为它把八个方向统一成了一组循环,避免你在检查时写八个 if 语句。如果不用数组,你可能会写出checkRight、checkLeft、checkDown... 八个函数,代码又长又容易在复制粘贴时改错一个符号。方向数组的排列顺序也值得养成固定习惯。我的习惯是先把水平方向写完、再写垂直方向、最后写四个斜方向,这样排查时不容易漏。你可以把方向表的注释直接写在数组旁边,比赛时能省下大量回忆时间。2.3 标记数组 vs 访问数组:这道题里它们的职责完全不同很多做过迷宫题的同学看到“标记”两个字,会条件反射地写 visited 数组,并在回溯时将其重置。这是 P1101 最大的陷阱之一。在迷宫题里,visited 数组的作用是防止同一轮搜索重复走同一个格子,所以在从一个分支退出时会“恢复现场”。在这道题里,我们要的不是探索路径,而是“验证一条已经固定的路径是否成立”。一旦验证成功,这条路径上的格子就要永久保留,不管之后其他路径是否经过它们,都不能再撤销。所以我更建议把标记数组命名为keep或used,语义是“这个格子最终要保留输出”。它不参与匹配过程,只在最后输出阶段起作用。匹配过程本身只依赖原矩阵board,不需要任何访问状态。3. 核心解法:方向枚举比 DFS 更实在3.1 算法流程只有四步整个算法可以压缩成四句话:遍历每一个格子;如果当前字母不是y,跳过;如果是y,就枚举八个方向;对每个方向检查从当前格开始沿该方向走七步,是否依次等于yizhong的七个字符,成立则标记路径。为什么要从y开始而不是从任意字母开始?因为目标单词首字母固定是y。以y为起点能剪掉大量无意义的检查。同理,你还可以在检查前先算一下“这个方向第七个位置是否越界”,但这不是必须的,完整循环里每一步都会做边界判断,越界自然返回失败。3.2 完整 C++ 实现及逐段说明先看代码,再逐段拆解。我习惯用#include <bits/stdc++.h>参赛,因为它能一次性覆盖常用头文件,省得比赛时纠结该引哪些库。函数命名尽量直白,方便赛后回看。#include <bits/stdc++.h> using namespace std; const int MAXN = 110; char board[MAXN][MAXN]; bool keep[MAXN][MAXN]; int n; string word = "yizhong"; int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1}; int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1}; bool canMatch(int x, int y, int d) { for (int k = 0; k < (int)word.size(); k++) { int nx = x + k * dx[d]; int ny = y + k * dy[d]; if (nx < 0 || nx >= n || ny < 0 || ny >= n) return false; if (board[nx][ny] != word[k]) return false; } return true; } void markPath(int x, int y, int d) { for (int k = 0; k < (int)word.size(); k++) { int nx = x + k * dx[d]; int ny = y + k * dy[d]; keep[nx][ny] = true; } } int main() { cin >> n; for (int i = 0; i < n; i++) cin >> board[i]; memset(keep, 0, sizeof(keep)); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (board[i][j] != 'y') continue; for (int d = 0; d < 8; d++) { if (canMatch(i, j, d)) { markPath(i, j, d); } } } } for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cout << (keep[i][j] ? board[i][j] : '*'); } cout << '\n'; } return 0; }这段代码需要解释几个关键点。第一,canMatch函数里的 k 循环从 0 到 6,恰好对应单词的七个字母。因为k * dx[d]会把当前位置逐步沿着方向 d 推进,所以不需要维护一个“当前坐标”变量。这样写的好处是:即使中途越界或字符不匹配,整个函数直接返回 false,不会污染任何状态。第二,markPath和canMatch的循环结构几乎一模一样,只是把比较字符换成了置标记。代码冗余但非常清晰,比赛时间紧张时,宁可用一点冗余换正确性。还有一点要注意:word.size()返回的是无符号整数,循环变量 k 可能被隐式转换,所以我在代码里写了(int)word.size()。这个问题平时不痛不痒,但在某些编译器警告全开的环境下会浪费时间。3.3 Python 实现版本如果你准备用 Python 参赛,这段逻辑可以写得更短。Python 的列表推导式在处理输出时很方便。n = int(input()) board = [list(input().strip()) for _ in range(n)] keep = [[False] * n for _ in range(n)] word = "yizhong" directions = [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)] def check(x, y, dx, dy): for k in range(len(word)): nx = x + k * dx ny = y + k * dy if not (0 <= nx < n and 0 <= ny < n): return False if board[nx][ny] != word[k]: return False return True def mark(x, y, dx, dy): for k in range(len(word)): keep[x + k * dx][y + k * dy] = True for i in range(n): for j in range(n): if board[i][j] == 'y': for dx, dy in directions: if check(i, j, dx, dy): mark(i, j, dx, dy) for i in range(n): print(''.join(board[i][j] if keep[i][j] else '*' for j in range(n)))Python 代码里我把directions写成了元组列表,读起来更直观。check和mark的实现与 C++ 完全等价。需要提醒的是,Python 的嵌套循环性能虽然一般,但这道题 n 最大只有 100,完全不需要担心超时。3.4 为什么每一步都查边界比先查终点更稳有些同学会把检查函数写成先判断终点是否越界,再判断中间字符。比如检查方向 d 时,直接计算第 6 步的坐标,如果越界就跳过。这种做法在 n 很小时没问题,但不够通用。更稳的写法是像上面这样,每一步都检查边界,因为一旦目标单词的长度不止 7 个字符,你的终点判断逻辑就得同步改,而逐步检查的代码只需要改一下word的内容。这种“让代码跟着数据走”的思维,在蓝桥杯备赛后期会越来越重要。毕竟省赛里很多题目都不只一组测试数据,边界条件稍有偏差就可能全盘皆输。4. 踩坑实录:我在第一版代码里犯的三个错误4.1 用 DFS 做路径搜索,方向越走越乱我的第一版代码大概长这样:从某个y开始,递归尝试八个方向,每次递归再重新枚举方向,试图拼出yizhong。结果就是,一条路径可能在第三个字符时换了个方向,比如向右走得到i,接着却向下走得到z,这时候程序还会继续比较,虽然单词长度累加上去了,但方向已经不对。最终输出的是:明明方阵里有正儿八经的横向单词,程序却把一条 L 形路径认成了匹配项。排查时我把每一步的坐标打印出来,才意识到问题出在“每层递归都重新枚举方向”。后来我改成在检查路径前先把方向定死,把方向参数传进检查函数,问题就消失了。这个坑之所以常见,是因为很多同学的条件反射就是先写 DFS。但 P1101 的题眼是“同一个方向连续走”,不是“通过不同方向拼接单词”。想通这一点,代码从递归 30 行变成了循环 15 行。4.2 错误恢复现场,导致已经匹配的路径被撤销第二版我吸取了教训,不再用 DFS,改用方向枚举。但我在标记时犯了一个错:把keep数组当成 visited,配合一个栈,在匹配失败时把已经打的标记全部撤销。正常匹配成功时没问题,但一旦某个方向检查到一半发现字符不对,就会把之前几个格子的标记清掉。这会导致一个非常隐蔽的 bug:如果同一格被两条路径共用,第一条路径成功打上了标记,第二条路径检查到一半失败,顺便把第一条的标记清掉,最终输出少保留了一些字母。排查这个问题时,我一度怀疑是方向数组写错了,后来用了一个只有两个重叠单词的小数据,发现keep数组的值在中途被改成 false,才意识到是恢复现场的锅。修复方法很简单:标记只在全部匹配成功后统一打上,匹配过程中不要碰keep数组。4.3 边界判断的坐标顺序和符号写反了第三个坑出现在坐标方向上。我的方向数组一开始写成了dx = {0, 0, 1, -1, 1, 1, -1, -1},dy = {1, -1, 0, 0, 1, -1, 1, -1},看起来和标准写法只是行、列互换了,但因为后面check函数里所有比较都用nx < 0 || nx >= n || ny < 0 || ny >= n,行列互换理论上也能通过。真正出问题的是其中一个斜方向,我把 (1, -1) 写成了 (1, 1),导致所有右上方向的单词全部漏掉。这种错误在样例上可能完全看不出来,因为样例不会把八个方向的单词都覆盖到。我后来养成一个习惯:自己构造一个包含八方向的小方阵,每个方向各放一个词,跑一遍程序,用肉眼对比输出。这个方法救了我很多次,强烈建议你也试一下。5. 从 P1101 抽象出通用套路:字符矩阵直线匹配的一类题5.1 通用代码骨架:起点枚举 + 方向枚举 + 长度比较P1101 的解法可以抽象成一个四段式骨架,适用于所有“字符矩阵中找直线单词”的题目:定义好方向增量数组 dx / dy。遍历所有格子,找到和目标单词首字母相同的起点。枚举八个方向,对每个方向检查整条路径是否完全匹配。匹配成功则对路径上的格子做标记或计数。这个骨架几乎不需要改动就能应对很多变体。比如题目让你统计单词出现次数,只要在匹配成功时返回计数而不是打标记;再比如题目让你输出所有匹配起点的坐标,只要在匹配成功时记录 (i, j) 和方向 d。5.2 与可转弯的单词搜索题做对比很多题库里都有一道“单词搜索”题:给定一个字母网格和一个单词,判断单词是否可以通过相邻格子的路径组成,路径允许上下左右转弯。很多同学做完 P1101 后会把两道题混在一起,实际上它们的解法完全不同。下面这个表格能帮你快速区分:对比维度P1101 单词方阵可转弯单词搜索路径形状固定直线,方向不变可上下左右任意转弯核心算法方向枚举 + 字符串比对DFS + 回溯是否需要恢复现场不需要需要状态变量起点 + 方向当前坐标 + 已匹配长度典型复杂度O(n² × 8 × L)O(n² × 4^L)从这个表能看出来,P1101 的时间复杂度是线性的,而单词搜索是指数级的。所以遇到题目时,先判断路径能否转弯,再决定上不上 DFS。这个判断往往只需要 10 秒,却能避免你走一大段弯路。5.3 常见变形:多单词、四方向、回文串如果你把 target 从"yizhong"改成变量,代码立刻就能处理任意固定长度的单词。如果再进一步,把方向数组从八个方向改成四方向,就变成了“直线四方向”的简化版。还见过一种变体:不是找固定单词,而是找方阵里所有“长度为 m 的回文串”,本质上也还是直线枚举,只是终点的匹配条件从等于某个字符串变成了首尾相等。理解这些变形之后,你会发现 P1101 并不是一道孤立题,它属于“字符矩阵直线模式”这个大家族。备赛时可以按这个思路把类似题归类,这样刷一道题,就等于储备了一个题型的解法。6. 蓝桥杯备赛 Day1 复盘:时间分配与后续计划6.1 一道入门题应该花多久我给自己定的规则是:第一遍独立思考不超过 20 分钟,如果在 20 分钟内没有完整思路,就停下来看题解,但只看思路提示,不看完整代码,然后自己重新写。P1101 属于“15 分钟内应该能想到方向枚举”的题,如果你卡在 DFS 的泥潭里超过 20 分钟,说明需要把“直线匹配”和“路径搜索”这两类问题在脑子里分开。写完代码并提交通过后,再花 10 分钟做复盘:我自己第一版错在哪里?正确的题解比我的版本好在哪?如果把 n 扩大到 1000 还能不能跑?这三个问题能帮你把一道简单题的价值放到最大。6.2 从这道题提炼的备赛日志模板从 Day1 开始,我每天会写一个极简备赛日志,包含五个要点:日期、题号、知识点标签我的第一反应是否选对了算法标准解法的关键一步是什么本轮踩的坑及根因一句话总结该题型的记忆点这个模板不会花太多时间,但能让你在考前一周快速回滚所有题型。比如 P1101 的一句话记忆点,我会写:“直线单词不用 DFS,方向定死,七步比对,对了就标记。”考前翻到这句,整个解法就能在十几秒内重新浮现。备赛第一天的收获,不在于你真的掌握了一个算法,而在于你开始建立一种“刷题直觉”:看到字符矩阵先想方向数组,看到路径可转弯才想回溯。这种直觉需要大量题目去喂养,但起点就在 P1101 这道看似简单的单词方阵里。
1. 为什么第一天的第一题选了P1101 单词方阵如果今天是你的蓝桥杯备赛第一天,我建议你先把 P1101 单词方阵 这道题做透。原因很简单:它不是难题,却把搜索类题目里最容易被忽略的细节全暴露出来了——方向怎么枚举、边界怎么判、标记怎么打。我第一次做这道题时,大概花了三十分钟;回头看,真正有价值的不是提交通过那一刻,而是把“为什么这样写”想清楚的过程。这篇就按我 Day1 的实际备赛思路,从题目拆解、方向枚举、完整代码到踩坑复盘,完整过一遍。1.1 这道题在蓝桥杯备赛里的定位蓝桥杯省赛的题目大致分几类:填空、模拟、搜索、图论、动态规划。P1101 这种“字符矩阵里找单词”的题,属于搜索题里的入门精品。它不涉及复杂的剪枝,也不需要状态压缩或记忆化,但能把二维数组遍历、方向数组、边界判断一次练熟。备赛第一天选它,就像训练营开营先练站姿,后面跑动、对抗才有基础。很多同学觉得搜索题难,是因为一上来就碰迷宫最短路径、连通块数量,这些题既要用 DFS 又要处理访问标记和回溯,很容易劝退。但 P1101 把搜索题的骨架单独拎出来:你只需要从起点出发,沿着固定的方向,一步步核对字符是否匹配,全程没有任何决策分支。这种“低难度但高信息量”的题目,恰好适合做备赛第一天的热手题。1.2 读完题之后的第一反应:别急着写 DFS看到“单词方阵”四个字,多数人的第一反应是写 DFS 回溯:从某个字母出发,上下左右搜索,尝试拼出目标单词。但我必须泼一盆冷水:这道题用真正的 DFS,反而容易把自己绕晕。题目里的隐藏单词是yizhong,它在方阵中只能沿着八个方向之一直线放置。什么叫直线放置?就是从起点开始,一直往同一个方向走,中间不拐弯,连续拼出 y-i-z-h-o-n-g 这七个字母。换句话说,这条路径在起点和方向决定后就已经完全确定,不存在“下一步有多个选择”的情况。DFS 回溯的价值在于处理“多分支、可回退”的状态空间,而这里根本没有分支,硬上回溯只会让你在方向判断上做大量无用功。所以正确的心理建设是:这道题真正要练的是“方向枚举 + 字符串比对”。把八个方向看成八条可能的路径,每个方向都试一遍,能匹配就打个标记。想明白这一点,代码量瞬间少一半。1.3 样例输入里藏着的关键信息题目输入第一行是方阵边长 n,后面跟着 n 行字母。输出是一个同样大小的方阵:如果某个字母属于某个完整的yizhong单词,就原样输出;否则输出星号*。这里有一个容易被忽略的细节:同一块字母可能被多条匹配路径共用。比如两个单词从不同起点出发,在中途某几个格子上重叠,这些格子仍然应该保留。因为你最终判断的是“这个格子是否属于任意一条完整路径”,而不是“这个格子是否只属于一条路径”。这也决定了解法里要单独开一个标记数组,而不是在匹配失败时把标记撤销。另一个细节是方向一致性:从起点到终点,七个字母必须严格朝同一方向排列。有的同学写检查函数时,每走一步都重新枚举方向,结果把直线路径走成了折线路径,这种错误在样例上经常看不出来,但一提交就错。2. P1101 题目到底在说什么:输入、输出与规则拆解2.1 字母方阵的规则与隐藏单词P1101 的题干用一句话概括是:给你一个 n×n 的字母方阵,方阵里可能藏着若干个单词yizhong,它们可以横着、竖着、斜着排列,但每条单词必须是一条直线。你需要做的,是把所有属于这些单词的字母保留下来,把不相关的字母改成星号。n 的范围在常见评测数据里一般不超过 100,所以最暴力的复杂度也是 O(n² × 8 × 7),也就是最多几十万次字符比较,在任何语言里都能轻松通过。理解这一点很重要:如果你在写这道题时还想各种剪枝优化,那就是把一门心思都花错了地方。蓝桥杯省赛里很多题的时间限制其实很宽松,关键不是把程序跑得多快,而是把规则理解得足够准确。为了让你真正吃透规则,我随手设计一个最小可验证场景。假设 n=7,方阵第一行正好放了一个横向的yizhong,主对角线上又放了一个斜向的yizhong,它俩在y这个起点上重叠。正确输出里,第一行和对角线上的字母都应该被保留,其他格子全是*。如果你写的程序总是漏掉斜向或重叠的字母,多半就是规则理解出了问题。2.2 八方向增量表是整道题的灵魂二维方阵中,从某个格子 (x, y) 出发,可以走八个方向。为了在代码里统一处理,一般会用方向增量数组表示:水平向右:(1, 0)水平向左:(-1, 0)垂直向下:(0, 1)垂直向上:(0, -1)右下方:(1, 1)左上方:(-1, -1)右上方:(1, -1)左下方:(-1, 1)这里使用的坐标约定是 x 表示行下标、y 表示列下标。不同的编程习惯可能把行和列反过来,但只要你始终保持“同一套坐标体系”,结果都一样。为什么方向增量数组这么重要?因为它把八个方向统一成了一组循环,避免你在检查时写八个 if 语句。如果不用数组,你可能会写出checkRight、checkLeft、checkDown... 八个函数,代码又长又容易在复制粘贴时改错一个符号。方向数组的排列顺序也值得养成固定习惯。我的习惯是先把水平方向写完、再写垂直方向、最后写四个斜方向,这样排查时不容易漏。你可以把方向表的注释直接写在数组旁边,比赛时能省下大量回忆时间。2.3 标记数组 vs 访问数组:这道题里它们的职责完全不同很多做过迷宫题的同学看到“标记”两个字,会条件反射地写 visited 数组,并在回溯时将其重置。这是 P1101 最大的陷阱之一。在迷宫题里,visited 数组的作用是防止同一轮搜索重复走同一个格子,所以在从一个分支退出时会“恢复现场”。在这道题里,我们要的不是探索路径,而是“验证一条已经固定的路径是否成立”。一旦验证成功,这条路径上的格子就要永久保留,不管之后其他路径是否经过它们,都不能再撤销。所以我更建议把标记数组命名为keep或used,语义是“这个格子最终要保留输出”。它不参与匹配过程,只在最后输出阶段起作用。匹配过程本身只依赖原矩阵board,不需要任何访问状态。3. 核心解法:方向枚举比 DFS 更实在3.1 算法流程只有四步整个算法可以压缩成四句话:遍历每一个格子;如果当前字母不是y,跳过;如果是y,就枚举八个方向;对每个方向检查从当前格开始沿该方向走七步,是否依次等于yizhong的七个字符,成立则标记路径。为什么要从y开始而不是从任意字母开始?因为目标单词首字母固定是y。以y为起点能剪掉大量无意义的检查。同理,你还可以在检查前先算一下“这个方向第七个位置是否越界”,但这不是必须的,完整循环里每一步都会做边界判断,越界自然返回失败。3.2 完整 C++ 实现及逐段说明先看代码,再逐段拆解。我习惯用#include <bits/stdc++.h>参赛,因为它能一次性覆盖常用头文件,省得比赛时纠结该引哪些库。函数命名尽量直白,方便赛后回看。#include <bits/stdc++.h> using namespace std; const int MAXN = 110; char board[MAXN][MAXN]; bool keep[MAXN][MAXN]; int n; string word = "yizhong"; int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1}; int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1}; bool canMatch(int x, int y, int d) { for (int k = 0; k < (int)word.size(); k++) { int nx = x + k * dx[d]; int ny = y + k * dy[d]; if (nx < 0 || nx >= n || ny < 0 || ny >= n) return false; if (board[nx][ny] != word[k]) return false; } return true; } void markPath(int x, int y, int d) { for (int k = 0; k < (int)word.size(); k++) { int nx = x + k * dx[d]; int ny = y + k * dy[d]; keep[nx][ny] = true; } } int main() { cin >> n; for (int i = 0; i < n; i++) cin >> board[i]; memset(keep, 0, sizeof(keep)); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (board[i][j] != 'y') continue; for (int d = 0; d < 8; d++) { if (canMatch(i, j, d)) { markPath(i, j, d); } } } } for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cout << (keep[i][j] ? board[i][j] : '*'); } cout << '\n'; } return 0; }这段代码需要解释几个关键点。第一,canMatch函数里的 k 循环从 0 到 6,恰好对应单词的七个字母。因为k * dx[d]会把当前位置逐步沿着方向 d 推进,所以不需要维护一个“当前坐标”变量。这样写的好处是:即使中途越界或字符不匹配,整个函数直接返回 false,不会污染任何状态。第二,markPath和canMatch的循环结构几乎一模一样,只是把比较字符换成了置标记。代码冗余但非常清晰,比赛时间紧张时,宁可用一点冗余换正确性。还有一点要注意:word.size()返回的是无符号整数,循环变量 k 可能被隐式转换,所以我在代码里写了(int)word.size()。这个问题平时不痛不痒,但在某些编译器警告全开的环境下会浪费时间。3.3 Python 实现版本如果你准备用 Python 参赛,这段逻辑可以写得更短。Python 的列表推导式在处理输出时很方便。n = int(input()) board = [list(input().strip()) for _ in range(n)] keep = [[False] * n for _ in range(n)] word = "yizhong" directions = [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)] def check(x, y, dx, dy): for k in range(len(word)): nx = x + k * dx ny = y + k * dy if not (0 <= nx < n and 0 <= ny < n): return False if board[nx][ny] != word[k]: return False return True def mark(x, y, dx, dy): for k in range(len(word)): keep[x + k * dx][y + k * dy] = True for i in range(n): for j in range(n): if board[i][j] == 'y': for dx, dy in directions: if check(i, j, dx, dy): mark(i, j, dx, dy) for i in range(n): print(''.join(board[i][j] if keep[i][j] else '*' for j in range(n)))Python 代码里我把directions写成了元组列表,读起来更直观。check和mark的实现与 C++ 完全等价。需要提醒的是,Python 的嵌套循环性能虽然一般,但这道题 n 最大只有 100,完全不需要担心超时。3.4 为什么每一步都查边界比先查终点更稳有些同学会把检查函数写成先判断终点是否越界,再判断中间字符。比如检查方向 d 时,直接计算第 6 步的坐标,如果越界就跳过。这种做法在 n 很小时没问题,但不够通用。更稳的写法是像上面这样,每一步都检查边界,因为一旦目标单词的长度不止 7 个字符,你的终点判断逻辑就得同步改,而逐步检查的代码只需要改一下word的内容。这种“让代码跟着数据走”的思维,在蓝桥杯备赛后期会越来越重要。毕竟省赛里很多题目都不只一组测试数据,边界条件稍有偏差就可能全盘皆输。4. 踩坑实录:我在第一版代码里犯的三个错误4.1 用 DFS 做路径搜索,方向越走越乱我的第一版代码大概长这样:从某个y开始,递归尝试八个方向,每次递归再重新枚举方向,试图拼出yizhong。结果就是,一条路径可能在第三个字符时换了个方向,比如向右走得到i,接着却向下走得到z,这时候程序还会继续比较,虽然单词长度累加上去了,但方向已经不对。最终输出的是:明明方阵里有正儿八经的横向单词,程序却把一条 L 形路径认成了匹配项。排查时我把每一步的坐标打印出来,才意识到问题出在“每层递归都重新枚举方向”。后来我改成在检查路径前先把方向定死,把方向参数传进检查函数,问题就消失了。这个坑之所以常见,是因为很多同学的条件反射就是先写 DFS。但 P1101 的题眼是“同一个方向连续走”,不是“通过不同方向拼接单词”。想通这一点,代码从递归 30 行变成了循环 15 行。4.2 错误恢复现场,导致已经匹配的路径被撤销第二版我吸取了教训,不再用 DFS,改用方向枚举。但我在标记时犯了一个错:把keep数组当成 visited,配合一个栈,在匹配失败时把已经打的标记全部撤销。正常匹配成功时没问题,但一旦某个方向检查到一半发现字符不对,就会把之前几个格子的标记清掉。这会导致一个非常隐蔽的 bug:如果同一格被两条路径共用,第一条路径成功打上了标记,第二条路径检查到一半失败,顺便把第一条的标记清掉,最终输出少保留了一些字母。排查这个问题时,我一度怀疑是方向数组写错了,后来用了一个只有两个重叠单词的小数据,发现keep数组的值在中途被改成 false,才意识到是恢复现场的锅。修复方法很简单:标记只在全部匹配成功后统一打上,匹配过程中不要碰keep数组。4.3 边界判断的坐标顺序和符号写反了第三个坑出现在坐标方向上。我的方向数组一开始写成了dx = {0, 0, 1, -1, 1, 1, -1, -1},dy = {1, -1, 0, 0, 1, -1, 1, -1},看起来和标准写法只是行、列互换了,但因为后面check函数里所有比较都用nx < 0 || nx >= n || ny < 0 || ny >= n,行列互换理论上也能通过。真正出问题的是其中一个斜方向,我把 (1, -1) 写成了 (1, 1),导致所有右上方向的单词全部漏掉。这种错误在样例上可能完全看不出来,因为样例不会把八个方向的单词都覆盖到。我后来养成一个习惯:自己构造一个包含八方向的小方阵,每个方向各放一个词,跑一遍程序,用肉眼对比输出。这个方法救了我很多次,强烈建议你也试一下。5. 从 P1101 抽象出通用套路:字符矩阵直线匹配的一类题5.1 通用代码骨架:起点枚举 + 方向枚举 + 长度比较P1101 的解法可以抽象成一个四段式骨架,适用于所有“字符矩阵中找直线单词”的题目:定义好方向增量数组 dx / dy。遍历所有格子,找到和目标单词首字母相同的起点。枚举八个方向,对每个方向检查整条路径是否完全匹配。匹配成功则对路径上的格子做标记或计数。这个骨架几乎不需要改动就能应对很多变体。比如题目让你统计单词出现次数,只要在匹配成功时返回计数而不是打标记;再比如题目让你输出所有匹配起点的坐标,只要在匹配成功时记录 (i, j) 和方向 d。5.2 与可转弯的单词搜索题做对比很多题库里都有一道“单词搜索”题:给定一个字母网格和一个单词,判断单词是否可以通过相邻格子的路径组成,路径允许上下左右转弯。很多同学做完 P1101 后会把两道题混在一起,实际上它们的解法完全不同。下面这个表格能帮你快速区分:对比维度P1101 单词方阵可转弯单词搜索路径形状固定直线,方向不变可上下左右任意转弯核心算法方向枚举 + 字符串比对DFS + 回溯是否需要恢复现场不需要需要状态变量起点 + 方向当前坐标 + 已匹配长度典型复杂度O(n² × 8 × L)O(n² × 4^L)从这个表能看出来,P1101 的时间复杂度是线性的,而单词搜索是指数级的。所以遇到题目时,先判断路径能否转弯,再决定上不上 DFS。这个判断往往只需要 10 秒,却能避免你走一大段弯路。5.3 常见变形:多单词、四方向、回文串如果你把 target 从"yizhong"改成变量,代码立刻就能处理任意固定长度的单词。如果再进一步,把方向数组从八个方向改成四方向,就变成了“直线四方向”的简化版。还见过一种变体:不是找固定单词,而是找方阵里所有“长度为 m 的回文串”,本质上也还是直线枚举,只是终点的匹配条件从等于某个字符串变成了首尾相等。理解这些变形之后,你会发现 P1101 并不是一道孤立题,它属于“字符矩阵直线模式”这个大家族。备赛时可以按这个思路把类似题归类,这样刷一道题,就等于储备了一个题型的解法。6. 蓝桥杯备赛 Day1 复盘:时间分配与后续计划6.1 一道入门题应该花多久我给自己定的规则是:第一遍独立思考不超过 20 分钟,如果在 20 分钟内没有完整思路,就停下来看题解,但只看思路提示,不看完整代码,然后自己重新写。P1101 属于“15 分钟内应该能想到方向枚举”的题,如果你卡在 DFS 的泥潭里超过 20 分钟,说明需要把“直线匹配”和“路径搜索”这两类问题在脑子里分开。写完代码并提交通过后,再花 10 分钟做复盘:我自己第一版错在哪里?正确的题解比我的版本好在哪?如果把 n 扩大到 1000 还能不能跑?这三个问题能帮你把一道简单题的价值放到最大。6.2 从这道题提炼的备赛日志模板从 Day1 开始,我每天会写一个极简备赛日志,包含五个要点:日期、题号、知识点标签我的第一反应是否选对了算法标准解法的关键一步是什么本轮踩的坑及根因一句话总结该题型的记忆点这个模板不会花太多时间,但能让你在考前一周快速回滚所有题型。比如 P1101 的一句话记忆点,我会写:“直线单词不用 DFS,方向定死,七步比对,对了就标记。”考前翻到这句,整个解法就能在十几秒内重新浮现。备赛第一天的收获,不在于你真的掌握了一个算法,而在于你开始建立一种“刷题直觉”:看到字符矩阵先想方向数组,看到路径可转弯才想回溯。这种直觉需要大量题目去喂养,但起点就在 P1101 这道看似简单的单词方阵里。