
看到“超级玛丽游戏”这个题名老OI人应该都会心一笑——这哪是要你写一个能跳能踩蘑菇的马里奥而是要求你在屏幕上原样输出一张固定的字符画。很多第一次接触信息学奥赛的同学刷题列表里第一道题往往就是它。题目编号里的“1.1”说明它属于基础篇第一组“10”则表明它是这一组里靠后的题号。但你别因为它是个“输出题”就轻视恰恰是这种零逻辑、纯输出的题最能暴露一个新手在格式、细节和工具链上的短板。这篇就把这道题从题目分析、代码实现到提交避坑完完整整拆开讲一遍。1. 题目拆解这题到底在考什么1.1 字面任务输出一张固定字符画先看任务本质程序运行后在终端或者评测系统的标准输出里逐行打印一个由星号、井号、点号、空格组成的“超级玛丽”像素图形。图形本身是固定的所有行、所有列的内容题目里都给得明明白白不需要你算、不需要你判断、不需要你输入任何数据。你的程序只要做一件事——把这个图形一个字符不差地打出来。这道题放在NOI基础篇里目的非常纯粹检验你有没有掌握程序最基本的输出语句以及是否理解了“标准输出”这个概念。说得再直白点就是看你知不知道怎么让程序往外“说话”。很多没接触过算法竞赛的同学会问这也太简单了吧不就是复制粘贴一个字符串吗如果只是复制粘贴那确实不难。难的地方在于你在自己电脑上跑出来的效果和在评测系统里看到的判定结果完全是两码事。本地终端可能容忍行尾多一个空格评测系统则可能因为某个不可见字符直接给你判错。1.2 真正要考的两项硬功夫第一项硬功夫是“换行控制”。字符画是分行的每一行结尾必须精确地输出换行符不能多也不能少。用printf写\n用cout写endl都能达到目的。但如果你把某一行遗漏了换行你会发现两行图形粘在一起整个图案彻底变形。第二项硬功夫是“复制粘贴的准确度”。说得难听一点这道题就是一道“眼力题”。字符画中每一行的前面有多少个空格、中间是#还是*还是.都必须和题目给定的完全一致。哪怕某一行开头少了一个空格图案整体都会左移一位提交结果就是 Wrong Answer。你如果用肉眼逐行比对看半个小时未必能看出问题在哪但评测机只需要一微妙就能检测出差错。1.3 为什么这道题成了“新手劝退题”有个特别有意思的现象越是看起来没技术含量的题AC率反而可能越低。原因很简单——新手的第一道题往往就卡在环境配置和输出细节上。有人IDE装好之后写的第一个程序不是“Hello World”而是一个复杂的字符画。他可能卡在很多莫名其妙的地方编译报错、编码不对、本地运行正常但提交就编译失败、甚至有人把整个图形作为代码注释粘进去了。这道题最能筛掉一种人不读题、不检查、拿到样例就往代码里塞的人。等评测结果出来发现是Wrong Answer你再回头仔细看才意识到样例里的图形最后一行之后还有一个换行或者某个长行的末尾藏着两个空格。所以我把这道题称作“新手劝退题”不是因为它难而是因为它用最简单的方式告诉所有刚入坑的人在信息学竞赛里程序的输出必须和标准答案“一个字节不差”。2. 多种解法与选型思路2.1 最无脑也最稳的方案printf逐行输出第一种方案也是最直接的一种就是用printf把图形的每一行单独打出来。你打开代码编辑器从第一行开始把字符画一行一行写进两个双引号之间。优点是完全贴合题目哪一行有问题改哪一行绝对不容易乱。#include cstdio int main() { printf( ********\n); printf( ************\n); printf( ####....#.\n); printf( #..###.....##....\n); printf( ###.......######\n); printf( ...........\n); printf( ###########\n); printf( ## ####\n); printf( ## ####\n); printf( ####\n); printf( ##\n); return 0; }这里要注意每个printf里的字符串末尾都带着\n最后的return 0;别忘了写。有些评测系统不要求返回非零值但养成写return 0;的习惯没有坏处。我在带新手的时候从来都鼓励他们用方案一先跑通一次。原因很简单第一版代码的容错率最重要搞一堆花花肠子一旦出错你根本不知道从哪查起。2.2 用字符数组加循环规范又省力如果你已经学了数组和循环完全可以换一种风格。把每一行看作一个字符串存进一个字符串数组然后用循环逐行输出。这种做法的优势在于代码看起来更“程序化”也更能体现你掌握了批量操作的思想。#include cstdio int main() { char* lines[] { ********, ************, ####....#., #..###.....##...., ###.......######, ..........., ###########, ## ####, ## ####, ####, ## }; int n sizeof(lines) / sizeof(lines[0]); for (int i 0; i n; i) { printf(%s\n, lines[i]); } return 0; }本质上两种写法输出的结果一模一样区别只是代码的组织方式。在“超级玛丽”这道题上二者没有性能差异纯粹看个人习惯。我更推荐第二种因为当你后面遇到“输出一张九九乘法表”或者“输出菱形图案”这类题时循环加数组的思路能直接迁移过去。你可以把字符画理解成一张由字符串组成的表每一行是表里的一行记录循环就是遍历这张表。2.3 另类思路raw string一字不差输出C11以后提供了一种原始字符串字面量raw string literal用R(...)包裹内容括号内的字符全部按原样输出不需要转义任何东西。也就是说你可以把整个字符画直接粘在代码里连\n都不用写。#include cstdio int main() { puts(R( ******** ************ ####....#. #..###.....##.... ###.......###### ........... ########### ## #### ## #### #### ##)); return 0; }这个方案极其适合“复制粘贴型”选手因为图形是整块搬进来的不容易因为某一行忘了写\n而出错。但有一个前提你的编译器必须支持C11标准。早年有些信息学竞赛环境还在用老旧的编译器对R(...)支持不好。稳妥起见如果你不确定评测环境的C标准还是用前两种方案吧。3. 实操复现从零到AC的完整过程3.1 本地开发环境的准备想把这题跑通你至少需要一个C/C编译环境。竞赛圈用得比较多的是在Windows下装一套集成开发环境里面既带编译器又带编辑器。很多新手会卡在配置这一步比如环境变量没配好或者在IDE里点“运行”却提示找不到编译器。这里给一条最省事的路如果你在Windows上装一个Dev-CPP或者同为开源免费的工具装完就能编译如果你用Mac或Linux系统自带的g命令经常会帮上大忙。我不建议新手上来的第一道题就折腾复杂的配置。先把环境弄到“能编译、能运行、能看到输出”的程度就足够了。等以后遇到真正的工程型题目再去研究调试器、断点、监视变量这些高级功能。这里有个小细节在比赛评测系统里很多都采用Linux环境而你本地是Windows环境。Windows下写\n没问题但有些Windows编辑器会在你敲回车时自动插入\r\n。如果你把代码文件保存后再提交某些评测系统会自动处理换行符但有些不会。为了避免踩坑提交前一定要确认代码文件里没有混入莫名其妙的回车符。3.2 从题目页面复制图形时的三个关键操作第一步把题目样例输出区域里的图形完整选中。这一步看起来毫无技术含量实际上出错率极高。尤其当你用鼠标从第一行拉到最后一行的过程中很容易把最后一行的换行漏掉或者把某一行行首的空格选丢。我的建议是先把样例输出粘贴到记事本里再随意选几行看看行首空格是否都在然后再从记事本复制到代码编辑器。第二步注意行首空格的数量。有的图形第一行前面有16个空格第二行前面有15个空格每行都不一样。如果你在代码里手动敲这些空格极容易数错。高手通常不会肉眼去数而是把示例文本复制到代码里让编辑器保留原始空格。这里分享一个实操小技巧在大多数现代编辑器里你可以选中一段含空格的文本按Tab键会在行首统一加上一个制表符按ShiftTab则统一减少。你完全可以利用这个功能修正整体的缩进偏差。第三步确认图形里每个字符的类型。字符画里的.、#、*都不是乱码也不是中文句号或中文井号。得检查你粘进去的是半角英文字符。很多输入法会在你输入中文时自动把标点替换成全角符号。全角字符和半角字符在视觉上区别很大但在某些窄字体下看起来却很像。我就见过不少人把代码里的#输成了中文“”编译都过不了还一脸茫然。3.3 提交前的三个自检步骤本地运行通过不代表评测一定通过提交前你要做三件事。第一打开一个干净的终端只运行你的程序确认输出里没有多余的提示语。很多新手喜欢在代码里加一句“请输入数据”或者“这里是超级玛丽”结果这些信息也被当成输出的一部分直接导致判错。记住评测系统只关心标准输出里的内容其他一切多余字符串都是致命伤。第二检查文件保存格式。保存代码文件时编码选择UTF-8通常最保险。有些老款IDE默认把文件存成GBK编码一旦源码里有中文注释或中文符号提交到Linux评测环境就可能编译报错。你拿不准的时候干脆代码里不写任何中文纯英文和数字最安全。第三手动数一下输出行数。字符画一共有多少行你的程序就打印多少行一个不多一个不少。本地跑完后可以在命令行里执行./main | wc -l这样的命令统计行数Windows下也可以在IDE里看输出框的行号。如果行数对不上说明你的字符串数组里多了一行或漏了一行。4. 常见问题与排查技巧实录4.1 编译错误到底哪里少了分号新手遇到编译错误时可以先看报错信息指向哪一行。printf这条语句经常出现的问题是双引号没写全、末尾分号写成了中文分号、或者某个字母敲错。用自己敲代码的方式去逐字核对效率很低。我建议你掌握一个技巧把编译器报错信息里提示的那一行代码复制出来和标准写法做逐字符对比。以printf为例完整结构是printf(字符串);先检查有没有(再检查有没有成对最后看末尾有没有;。这就能解决一半的编译问题。4.2 本地能跑提交却是编译失败这种情况十有八九是代码文件里混进了不可见字符或者保存编码有问题。还有一种情况是你把代码写在了某个私有目录下提交时选了错误的文件。听起来很蠢但我见过很多次。A同学做这道题的时候本地创建了main.cpp复制到评测系统的提交框里时不小心把另一份写着“helloworld”的代码覆盖进去了。所以提交时一定要确认提交框里的代码和本地最终版本一致。另一个比较隐蔽的原因是你在代码里用了using namespace std;但在评测系统里和某个内置名字冲突。正常情况下不会出问题可一旦出了你就要检查是不是把什么自定义函数名定义成了main以外的系统关键字。对这个题来说最强的规避方式就是只用cstdio库不引入多余头文件。4.3 图形差一个字符怎么找都发现不了这是最让人抓狂的情况。图案看起来完全正确但提交效果就是Wrong Answer。那时候你会觉得评测系统是不是坏了甚至怀疑人生。实际上问题多半出在“你看到的空格”和“实际输出的空格”不一致上。第一检查行尾空格。有些代码在复制图形时每一行末尾都带了一个看不见的空格而题目里该行末尾并没有空格。或者反过来题目里该行末尾有空格但你的代码复制时把末尾空格删了。第二检查全角空格。某些输入法在中文状态下敲空格会输出一个全角空格它的ASCII码和普通空格完全不同但肉眼几乎无法识别。第三检查制表符。如果你复制的时候用了Tab缩进代码里就可能在字符串中混入\t。它显示出来的宽度可能恰好让你觉得“对齐了”实则字符完全不对。破局的办法很简单把题目给出的图形保存为文本文件再把你程序输出的结果保存为另一个文本文件用文本对比工具做逐行差异比对。没有工具的话可以在命令行里用diff命令。它会非常明确地告诉你第几行第几个字符不一样。样例图形只有几行完全手动比对也能搞定但如果你养成了用diff的习惯以后几百行的输出题再也不会慌。4.4 换行符引发的悬案Windows下写文件每行结尾是回车加换行两个字符\r\nLinux下只有换行一个字符\n。如果你在Windows记事本里把图形复制到代码编辑器可能每行末尾都带了一个不可见的\r你打印时就会在每行多出一个回车符。这个回车符不会让图形“看上去”变化太多但它会导致输出结果在二进制的层面和标准答案不一致。规避方法也简单第一用专业的代码编辑器替代系统自带记事本它们通常默认用LF换行第二如果你发现自己提交后提示Presentation Error或Wrong Answer又确定字符都没错优先排查换行符。很多评测系统会宽容地忽略行尾空格但不会忽略行尾的回车符所以这个问题必须重视。4.5 常见错误速查表现象可能原因解决方式编译报错双引号、分号为全角字符切换英文输入法重敲编译报错文件名或路径含中文改名为纯英文保存本地有输出提交无输出提交框选错代码文件重新复制并对比Wrong Answer行首空格数量不对用diff对比样例Wrong Answer行尾多余空格检查每行末尾Wrong Answer输出多了一行提示语删除所有多余printfPresentation Error换行符不一致检查文件换行格式超时不可能发生检查是否写了死循环5. 从“超级玛丽”延伸出来的刷题经验5.1 做输出题也能积累算法思维很多人觉得输出题对算法思维毫无帮助我不同意。这道题让你理解了什么叫 “一次精确复现”。你写程序的过程其实是把“题目里的一个静态数据”转换成“程序执行后的一个动态输出”。以后你做模拟题本质上也是“复现”一个过程只不过复现的对象从静态图形变成了动态变化。理解“程序的输出是确定的”这一点对后续学习非常关键。5.2 时间分配和调试策略如果非要说这道题有什么难度那唯一的分水岭就是细心。一个有效的时间分配策略是把大部分时间放在“核对图形”上而不是放在“敲代码”上。有人两分钟敲完代码却在提交后反复错了十几次我们反过来花十分钟把图形核对清楚一次提交通过反而更省时间。我自己当年还踩过一个坑第一次提交的时候因为图形里有一行的开头的空格比样例少了一个整整排查了一个小时。后来我学会把所有有疑问的行单独打印出来用肉眼盯着列号去数。现在我写这种带固定格式输出的题早就不会傻乎乎地一行行人工比对了——直接上脚本或者利用编辑器的字符高亮把空格、制表符、回车全部显示出来。很多编辑器都有“显示空格和制表符”的功能快捷键先把这招学会能让你在这类题上少走一大半弯路。5.3 用这道题建立“一次通过”的信心信息学竞赛中有一个说法叫“首杀”也就是第一次提交就通过。能在简单题上达成一次通过对新手建立信心特别重要。但这需要你在提交前就养成完整的自查流程检查输出内容、检查是否有多余输出、检查字符类型、检查换行符。第四步检查提交的文件是否正确。第五步点击提交之后再回想一遍代码逻辑。这套流程如果能在简单题上熟练以后面对复杂题你的出错率会大幅降低。就我个人带人的经验而言凡是能一次通过“超级玛丽”的同学在后面写“字符串输出”类题目时通常都会很稳。相反那些在这道题上反复重交的同学往往不是能力问题而是性子太急连样例都没复制对就去提交了。竞赛比的不只是你做不做得出更比你在同等条件下谁更少失误。最后再安利一个细节如果你看到某个提交框旁边有“语言选择”的选项一定要选和你本地编译器一致的语言标准。有很多评测系统默认用C14或C17编译你就选和本地Dev-CPP对应的版本。没必要为了追求花哨而强行使用新的语法特性稳定的写法才是竞赛场上的王道。这道超级玛丽虽然简单但它是我见过最适合训练“输出洁癖”的题目——每一次通过都是对你严谨程度的一次认可。