蓝桥杯必考:打印十字图题解,一文掌握二维数组图形模拟算法 第一次在练题系统里看到“打印十字图”这道蓝桥杯历届试题大多数人的第一反应是这不就是个字符拼接题吗直接照着样例把字符串打出来不就行了真坐下来写代码才明白n 一变图形尺寸跟着变手写字符串根本不可行。这一题挂着“文字图形”的名头实际上考的是二维数组坐标建模、图形对称性和循环边界的控制力本质上是一道披着输出题外衣的模拟题。这篇文章我会把这道题彻底拆开从在纸上画图找规律开始一步步讲到可以稳定复现的完整代码顺便把文字图形类题目通用的解题套路一起梳理清楚正在备赛的朋友可以直接拿去用。我在文中采用的图形版本是常见的“中心十字 嵌套边框”结构整个图案以中心点向上下左右延伸出十字臂外面再套 n 层方框每层方框都是单字符宽。不同题库里这道题的样例细节可能略有差异比如四角的缺口形状、边框是否闭合但核心方法完全一致学会自己推演规律任何变体都能很快做出来。1. 先弄清楚这道题到底在考什么先明确题目要求输入一个正整数 n输出一个由$和.组成的正方形图案。图案的中心是一个十字往外是一层一层的边框边框层数由 n 决定。看起来这是个“输出题”但它的难点不在字符串处理而在以下四个点。第一图形的尺寸是动态的。n 每增加 1画布的长宽都跟着变不能靠硬编码字符串解决。第二图形具有双重对称性左右对称、上下对称如果利用好对称代码能简化一半也能用对称性自查对错。第三坐标计算是这类题最容易翻车的地方中心点、十字臂长度、每层边框到中心的距离任何一个数算错整个图案就歪了。第四字符画题的输出格式要求严格$和.一个都不能错行末不能有杂字符最后还要正确换行。为了更直观地看题目在说什么先给出 n 1 和 n 2 时我这份实现的输出效果。n 1 是 9 x 9 的图案......... ......... ..$$$$$.. ..$.$.$.. ..$$$$$.. ..$.$.$.. ..$$$$$.. ......... .........n 2 是 13 x 13 的图案............. ............. ..$$$$$$$$$.. ..$...$...$.. ..$.$$$$$.$.. ..$.$.$.$.$.. ..$$$$$$$$$.. ..$.$.$.$.$.. ..$.$$$$$.$.. ..$...$...$.. ..$$$$$$$$$.. ............. .............注意看无论 n 取几图案上下左右都是对称的而且四周有一圈.留白。很多人拿到这类题的第一反应是“我直接按行打印”但一旦 n 是变量唯一的可靠做法就是“先建一个二维字符数组按坐标填好再整体输出”。这也是我把这篇文章的第一部分放在“考点拆解”的原因先理解它考什么后面所有步骤才有意义。2. 不急着写代码先在纸上把规律扒干净文字图形题最忌讳一上来就敲键盘。我的习惯是先拿坐标纸或者纯文本编辑器手工画两到三组小规模样例把规律写下来。这道题的规律拆开看其实就三件事画布多大、中心在哪、每一圈怎么摆。2.1 第一步定画布尺寸先观察图形占用的实际行数。n 1 时图形主体是第 2 行到第 6 行共 5 行上下各有 2 行留白所以总行数是 5 2 2 9。n 2 时主体是第 2 行到第 10 行共 9 行上下各有 2 行留白总行数是 9 2 2 13。这里的“5 行”和“9 行”其实就是 4n1再算上上下各 2 行于是画布尺寸 L (4n1) 4 4n5。用 0 基下标的话行号和列号都是从 0 到 L-1。图形主体从第 2 行开始为什么要留 2 格这是为了让最外层的边框和画布边缘之间有一圈空位视觉上图案是“浮”在画布中央的不会顶到边界。2.2 第二步找中心画十字臂中心点坐在整个画布的正中央也就是行列都是 2n2 的位置。这个结论可以直接从 L/2 得到(4n5)/2 在整数除法下等于 2n2但更推荐写成 c 2n2含义更明确后面的计算都基于它。十字臂从中心出发向上下左右各延伸 n1 格。也就是说横向臂是中心行上从第 c-(n1) 列到第 c(n1) 列这一段纵向臂是中心列上从第 c-(n1) 行到第 c(n1) 行这一段。以 n 1 为例c 4n1 2那么横向臂是第 4 行的第 2 到第 6 列纵向臂是第 4 列的第 2 到第 6 行交叉点正好落在第 4 行第 4 列。这里有个容易想不明白的点为什么不把十字臂画到画布边缘原因很简单因为外层还要套边框十字臂太长会和边框粘在一起图案层次就乱了。n1 这个长度刚好让十字臂端点落在第一层边框附近既保证视觉上是连贯的十字又不会穿破外框。2.3 第三步一圈一圈加边框边框用变量 k 表示层数k 从 1 到 n。第 k 层边框距离中心的距离是 d 2k。画法很直接以中心点 c 为圆心在距离 d 的位置画一个正方形边框的四条边。具体来说就是上边第 c-d 行的第 c-d 列到第 cd 列全部填$下边第 cd 行的第 c-d 列到第 cd 列全部填$左边第 c-d 列的第 c-d 行到第 cd 行全部填$右边第 cd 列的第 c-d 行到第 cd 行全部填$。以 n 2 为例c 6。k 1 时 d 2画的是第 4 行、第 8 行、第 4 列、第 8 列围出的框k 2 时 d 4画的是第 2 行、第 10 行、第 2 列、第 10 列围出的框。两条边框之间恰好相隔 2 个空字符形成清晰的“回”字层次。这三步规律扒完之后你会发现整道题其实就是三个循环初始化画布、画十字臂、画 n 层边框。剩下的交给代码就行。这也正是这类题目的通用节奏规律越清楚代码越简单。3. 两种实现填数组和算坐标规律一旦明确实现方式就有很多种。我推荐的做法是“数组填充法”这也是遇到文字图形题时最通用、最好调试的办法。另一种“坐标规约法”不建数组直接逐行判断输出代码更短但对思维要求更高适合作为进阶理解。3.1 方案A数组填充法推荐直接放完整代码。C 版本如下#include iostream using namespace std; const int MAXN 105; char g[MAXN][MAXN]; int main() { int n; cin n; int L 4 * n 5; int c 2 * n 2; // 中心坐标 // 1. 整个画布初始化为 . for (int i 0; i L; i) for (int j 0; j L; j) g[i][j] .; // 2. 画中心十字臂 for (int i c - (n 1); i c (n 1); i) { g[c][i] $; g[i][c] $; } // 3. 画分圈边框 for (int k 1; k n; k) { int d 2 * k; for (int j c - d; j c d; j) { g[c - d][j] $; g[c d][j] $; } for (int i c - d; i c d; i) { g[i][c - d] $; g[i][c d] $; } } // 4. 输出 for (int i 0; i L; i) { for (int j 0; j L; j) cout g[i][j]; cout \n; } return 0; }Python 版本几乎可以逐行对照n int(input()) L 4 * n 5 c 2 * n 2 g [[.] * L for _ in range(L)] # 中心十字臂 for i in range(c - (n 1), c (n 1) 1): g[c][i] $ g[i][c] $ # 分圈边框 for k in range(1, n 1): d 2 * k for j in range(c - d, c d 1): g[c - d][j] $ g[c d][j] $ for i in range(c - d, c d 1): g[i][c - d] $ g[i][c d] $ for row in g: print(.join(row))这段代码的核心思路是“先铺底再按坐标填”。二维数组先全部铺成.然后只改$的位置避免了判断条件的复杂度。这种做法有几个好处一是逻辑分步每一段代码只管一件事出了 bug 容易定位二是适用范围广几乎所有文字图形题都能用这个思路套三是调试方便随时可以单独把某一圈打印出来看。为什么推荐数组填充而不是直接打印因为这类题一旦层数变多逐行输出时大脑需要同时处理“当前行处于哪一层、哪些列要输出$”两个维度很容易乱。数组填充把问题拆成了两个环节填的时候只管坐标输出的时候只管遍历互不干扰。对于比赛场景这种“拆环节”的思维方式能显著降低失误率。3.2 方案B坐标规约法进阶如果不建二维数组也可以遍历每个格子根据坐标判断该输出$还是.。判断条件由前面推的规律直接翻译过来要么落在中心十字臂上要么落在某一层边框上。n int(input()) L 4 * n 5 c 2 * n 2 for r in range(L): for col in range(L): dr abs(r - c) dc abs(col - c) ok False # 中心十字臂 if (dr 0 and dc n 1) or (dc 0 and dr n 1): ok True # 分圈边框 for k in range(1, n 1): if (dr 2 * k and dc 2 * k) or (dc 2 * k and dr 2 * k): ok True print($ if ok else ., end) print()这段代码没有显式的二维数组而是直接对每个格子套公式。它的优势是代码量更少也挺巧妙但缺点很明显每次判断要循环一遍所有 k时间复杂度是 O(L * L * n)n 稍大就会浪费不少计算。比赛时我更推荐数组填充法因为文字图形题的瓶颈从来不是性能而是正确性和调试效率。坐标规约法更适合在把所有规律都吃透之后用来加深理解或者炫技。4. 实测几组数据顺便聊聊踩过的坑代码写出来只是第一步验证和排错才是真正耗时的地方。我跑了几组数据也复盘了这些年带备赛时大家最常踩的坑整理在这里。4.1 边界值n 1 和 n 2 是对照组建议每次改完代码第一件事就是跑 n 1 和 n 2拿输出和手绘的样例逐字符核对。这两个值足够小肉眼能看全又足够代表性能验证十字臂和边框的坐标计算是否正确。n 1 的输出在前文已经给出核心检查点是中心行是否完整、十字臂端点是否落在边框上。n 2 的输出同样在上文检查点是两层边框的间距是否均匀、中心行是否从第 2 列一直延伸到第 10 列。如果这两个值通过再试 n 3也就是 17 x 17 的图案。这个尺寸无法一眼扫完所有字符但可以检查四个方向的对称性以及最外层边框到画布边缘是否正好留了 2 行空格。一个比较省力的做法是把输出保存成文件写一小段脚本把第 i 行和第 L-1-i 行逐字符比较左右对称则把第 j 列和第 L-1-j 列逐字符比较。对称性一旦通过基本可以确定坐标公式没有问题。4.2 常见翻车点第一数组开太小。L 4n5如果题目里 n 最大是 30那 L 就是 125数组至少开到 130 以上。有些人习惯性开 105结果 n 一大就越界程序直接崩溃或者输出乱码。建议统一根据题目上限把数组开到 MAXN 200 或者更大彻底杜绝这类低级问题。第二忘记初始化画布。char 数组如果不赋值里面是随机内容这时候只填一部分$就输出会出现一堆乱码一样的字符。所以“先全部置成.”这一步不能省而且必须在填十字臂之前完成。第三中心坐标和边界算错。中心写成了 (L-1)/2 没问题但有人会手误写成 L/2在 L 为奇数时 L/2 其实等于 (L-1)/2这题恰好没问题但容易让人忽略问题本身。更稳妥的做法是单独定义 c 2*n2后面所有坐标都以 c 为基准可读性也更好。第四循环边界多一或者少一。画边框时for (int j c - d; j c d; j)这一句小于等于很容易被写成小于结果边框闭合不了。这类问题用 n 1 的样例一眼就能照出来边框不完整基本就是边界条件写错。第五字符大小写和空格问题。题目要求的是$和.有的同学图省事用别的字符替代或者行末多打了空格在严格判题的系统里都会 WA。字符串题目对输出格式的要求是“逐字符准确”不要画蛇添足。第六输出换行问题。每行末尾输出\n而不是endl虽然两者结果一样但endl会强制刷新缓冲区数据量大的时候稍微慢一点。比赛里习惯用\n更稳妥。4.3 调试的小技巧如果图案不对别急着打 log先做三件事第一确认画布尺寸 L 对不对行数就应该是 4n5第二确认中心 c 是不是 (L-1)/2第三找最外层边框的行坐标它应该是 2 和 L-3。这三处对了绝大部分坐标问题都能暴露。另外在调试阶段可以临时把.显示成空格这样图形轮廓更清楚但提交前记得改回来。5. 文字图形题的通用套路这次总算摸清了“打印十字图”做完之后再回头看其他文字图形题会发现套路高度一致。我把这套方法总结成“看图五步法”后面遇到类似的题都可以照着走。第一步小规模手画。无论题目给出的是三角形、菱形、回字还是数字矩阵先动手画出 n 1、n 2 甚至 n 3 的图画的过程就是找规律的过程。第二步定画布尺寸。通过几组样例推出总行数列数公式这一步直接决定数组大小和循环范围。第三步找对称轴和特殊点。大多数图形题都有中心点、对角轴或者行间对称先确定中心坐标再确定图形的关键结构。第四步拆结构。把复杂图形拆成几个简单部分比如中心十字臂、分圈边框、四个角的区域每一部分单独用一段循环去填。第五步小规模验证。至少跑 n 1 和 n 2 两组数据逐字符核对再用对称性自查。这个套路可以套在很多相近题型上。比如打印菱形本质是曼哈顿距离问题从中心出发满足abs(dr) abs(dc) d的格子输出*其余输出空格d 由输入决定。再比如打印回字矩阵规律是取格子到上下左右四条边距离的最小值根据这个值决定填什么字符。还有数字直角三角、螺旋填充、蛇形矩阵都可以看作“二维数组坐标 规律填充”的变体。可以说只要掌握了坐标建模的思路文字图形题就不再是背模板的题而是真正考验观察力和代码基本功的题。我自己备赛的时候每遇到图形题都会强制自己在纸上画至少两组小规模样例再动笔。这个习惯帮我省下了大量调试时间后来做别的模拟题也受益因为“先观察、再动手”的思路一旦养成写代码会稳很多。希望这篇分享也能帮你把“打印十字图”这一类题彻底拿稳。