资讯详情

资讯详情

GESP二级机考“优美的数字”:拆位与整除判断全解析

9月的GESP二级机考一结束就有好几个学生来问我第三题“优美的数字”到底怎么做。说实话这道题名字听着挺玄乎但把题面一拆开你会发现它考的其实是非常基础也非常经典的一套组合拳把数字的每一位拆出来再逐个判断能不能整除原数。难的地方不在于循环不会写而在于你有没有想清楚0这个特殊数字以及循环里原始数据被悄悄改掉这类细节。这篇文章从题面理解、算法推导、完整代码再到考场避坑一次讲透。正在备考二级的同学可以照着敲一遍带考级班的老师也能直接把整套思路拿去给学生讲。1. 题目到底在考什么先读懂“优美的数字”1.1 题面还原与样例说明考后大家在群里对题面有过不少讨论结合多个回忆版本比较通行的说法是这样的一个正整数被称为“优美的数字”需要同时满足两个条件——第一它的每一位数字都不能是0第二它的每一位数字都能整除这个数本身。什么叫“整除”就是做除法余数为0用C写出来就是x % digit 0。举个例子128就是优美的数字因为百位的1、十位的2、个位的8都能整除128128除以1得128128除以2得64128除以8得16三次都除得尽。而29不是因为个位的9不能整除2929除以9会余2。更典型的反例是210它压根连第一个条件都过不了个位是00不能做除数自然谈不上一一验证。最后我把这个回忆版本整理成了一道大家都熟悉的输入输出风格题目方便下面直接展开讲解。完整题目样貌大致如下具体以官方试卷为准输入格式第一行一个正整数n表示接下来要判断的数字个数。随后n行每行一个正整数x范围在1到10^6之间。输出格式对每个x如果它是“优美的数字”输出一行YES否则输出一行NO。输入样例3 128 210 29输出样例YES NO NO这里要提醒一句有些同学备考时喜欢背原题但我更建议大家把重心放在“判断函数怎么写”上。因为哪怕是同一个题名不同年份、不同级别出的输入格式可能完全不同今年让你判断n个数明年可能让你统计区间[L, R]里的优美数字数量。核心逻辑不变变来变去的只是外面那层输入输出壳。1.2 考点拆解拆位、整除与“AND”逻辑这道题表面上是在考“优美”这个花哨概念实际上拆开就是三件事。第一件事是拆位也就是把一个多位数拆成一位一位的独立数字第二件事是整除判断判断每个数位digit是否满足x % digit 0第三件事是逻辑归纳——要求的是“每一位都满足”才输出YES这本质是一个“AND”逻辑而不是“OR”。很多同学前两步都会写恰恰栽在第三步只要有一位满足就把整个数字判成了YES结果样例都对不上。从GESP二级的出题习惯来看这种“数字性质题”非常典型后面我还会专门聊备考方向。这里先建立最基本的算法骨架读入x之后用循环从低位到高位把每一位取出来逐个验证全部通过才合法。判断过程中一旦遇到0或者遇到一个不整除的数就立即结束判断没有必要再往后看剩下的位了。2. 核心算法设计从思路到代码的推导过程2.1 拆位是这道题的地基拆位在整个小学到初中的编程竞赛里都是高频操作它的原理只有一条对10取模拿到个位整除10丢掉个位。比如拿128来说128 % 10等于8我们就拿到了个位8接着128 / 10等于12数字就变成了12再对12 % 10拿十位212 / 10变成1最后1 % 10拿到百位11 / 10变成0循环结束。整个过程就像你数一叠钱一次抽出一张放在旁边抽完为止只不过这里的“钱”是数字的每一位。用生活化的类比来记数字的十进制表示就像一列火车车厢%10是只打开最后一节车厢的门把里面的乘客拿出来/10则是把这节空车厢直接卸掉让后面的车厢往前补位。循环做下去所有车厢都会被检查一遍。理解了这个后面学进制转换、数字反转、回文数判断都会顺手很多。这里有一个非常关键的原则千万不要直接在原数x上做除法。因为在后面的判断里我们还需要用到完整的原数来求余数如果边拆位边改变x拆到一半x就已经变成0了后面的x % digit全部失去意义。正确的做法是定义一个临时变量tmp让tmp等于x循环里只改变tmpx始终保持原样。这一个细节能挡住至少三成以上的出错代码。2.2 把“全部满足”翻译成代码逻辑设计逻辑的时候我习惯用一个叫作“旗标”的布尔变量。它的英文说法flag中文叫法很多有人叫标记有人叫标志位。核心思想是先假设这个数字是合格的让flag等于true然后在循环里一旦发现不合格的证据立刻把flag改成false。循环结束后flag的最终值就代表这个数字是否优美。伪代码写出来非常清晰读入 x tmp x flag true while tmp 0: digit tmp % 10 tmp tmp / 10 // 两条淘汰条件digit是0或者digit不能整除x 如果 (digit 0 或者 x % digit ! 0): flag false 提前退出循环 如果 flag 为 true: 输出 YES 否则: 输出 NO这里最容易写错的一个顺序问题是判断条件里的两条子条件必须先判断digit是否为0再去执行x % digit。原因很简单任何数字除以0都是没有意义的C里面直接对0取模会引发运行时错误虽然有的编译器在简单整数除零时会让程序直接崩掉但更隐蔽的情况是运算结果未知导致整个判断逻辑错得莫名其妙。先写digit 0再用或运算把后面的取模短路掉才是安全的写法。2.3 输入范围与数据规模要不要用快读和long long题目给的数据范围是1到10^6也就是最多一百万。这个规模对C来说非常友好用最普通的cin x配合while循环拆位每个数最多循环7次全部处理完连毫秒都用不到。所以二级的同学完全不需要学什么快读优化把cin和cout用熟练就够了时间不会成为瓶颈。至于数据类型10^6用int完全装得下int最大能到约21亿。我的习惯是只要题目允许就统一声明成int因为二年级阶段int范围内几乎不会溢出。但我也见过有同学在函数参数里写成long long这也没有问题只是没必要。真正的重点是在读入、传参、取模这些环节保持类型一致别混用。3. 完整实现与运行验证3.1 C参考实现单文件可直接编译下面这份代码是完整的可运行版本包含头文件和主函数直接拷贝到Dev-C、CodeBlocks或者VS Code里都能跑。我特意把判断逻辑写在主函数内部因为二级机考的编程环境通常不要求你写出标准的多函数结构简洁直观、能通过测试才是第一位的。#include iostream using namespace std; int main() { int n; cin n; for (int i 0; i n; i) { int x; cin x; int tmp x; // 用临时变量去拆位 bool ok true; // 先假设是优美的 while (tmp 0) { int digit tmp % 10; // 取当前最低位 tmp tmp / 10; // 去掉这位 // 数字含0直接淘汰不能整除也淘汰 if (digit 0 || x % digit ! 0) { ok false; break; } } if (ok) { cout YES endl; } else { cout NO endl; } } return 0; }注意看第5行的cin n和第8行的循环变量i这两个地方都不能写错。有些同学容易把第一行的n直接当成后续要判断的数字写成了for (int i 0; i n; i) { int x n; ... }结果所有输入都只用同一个数判断前功尽弃。3.2 函数封装与区间统计版本如果题目不是“依次判断n个数”而是改成“统计区间[L, R]内优美数字的数量”直接在主函数里堆一整段循环也能做但代码会显得很臃肿。更推荐的做法是把“判断一个数是否优美”抽成一个函数既方便复用又让主函数层次清楚。这在考级编程题里是一个非常重要的意识把重复出现的判断逻辑封装起来。#include iostream using namespace std; bool isBeautiful(int x) { if (x 0) return false; int tmp x; while (tmp 0) { int digit tmp % 10; tmp / 10; if (digit 0 || x % digit ! 0) { return false; } } return true; } int main() { int L, R; cin L R; int cnt 0; for (int i L; i R; i) { if (isBeautiful(i)) { cout i ; cnt; } } cout endl cnt endl; return 0; }函数版本的返回值是bool类型函数名用isBeautiful这种“is开头”的命名读起来就是“是不是优美数字”语义很清楚。这个函数不止能用在区间统计里将来遇到“找出第k个优美数字”“求优美数字的平方和”这类变体只要判断逻辑不变照样可以复用同一个函数。平时写代码把这个习惯养成了考场上改题会快很多。3.3 Python参考实现备考Python方向的同学看这里GESP分为C和Python两条路线如果你考的是Python核心逻辑完全一样只是语法表达不同。Python里同样的拆位思路写出来是这样def is_beautiful(x): tmp x while tmp 0: digit tmp % 10 tmp // 10 if digit 0 or x % digit ! 0: return False return True n int(input()) for _ in range(n): x int(input()) print(YES if is_beautiful(x) else NO)Python版本的几个细节值得说清楚。tmp // 10是整除赋值也就是tmp tmp // 10的简写因为Python里/是浮点除法直接tmp tmp / 10会让12变成12.0从而陷入死循环所以整除符号//绝对不能省。另外Python没有传统C那样的for (int i 0; i n; i)写法用for _ in range(n)来循环n次更简洁循环变量用下划线表示“我暂时用不到它”。这两处是C选手转身写Python时最容易踩的语法坑。3.4 多组测试数据的运行验证代码写完必须先用手算验证不能直接提交。我一般会准备一组覆盖各种边界情况的测试数据包括最小数字1、含0的数字、个位不整除的数、所有位数都相同的数字、还有最大的7位数。把这组数据跑一遍得到的输出和手推结果完全一致才敢点提交。输入数字拆位过程判断结果正确输出1先取11能整除1通过YES122、12和1都能整除12YES211、22不能整除21NO100、1含0直接淘汰NO1000、0、1含0直接淘汰NO1211、2、12不能整除121NO1111、1、1每一位1都能整除111YES1288、2、18、2、1都能整除128YES这张表最想说明的一点是手推的时候人是从高位往低位看的但程序拆位是从低位往高位拆的两者顺序相反却完全不影响判断结果因为这道题只关心“每一位都要满足”不关心顺序。理解了这个你就不会被程序里的拆数顺序绕晕。4. 考场上最容易掉进的五个坑4.1 第一个坑循环里改坏了原始数据这是最常见也最可惜的错误。有学生写成这样while (x 0) { int digit x % 10; x x / 10; if (x % digit ! 0) { ... } }表面上看while循环条件用x本身没什么问题拆位也确实拆出来了但循环结束后x已经变成0。更致命的是循环内部每一轮还用已经被修改过的x去做x % digit这时候的x早就不是原来的那个数了判断结果完全乱套。解决办法就是永远用临时变量tmp x去拆位原数x从头到尾保持不动。4.2 第二个坑0的处理顺序不对还有同学知道要判断0但把顺序写反了if (x % digit ! 0 || digit 0) { ok false; }C判断或条件时是从左往右执行的如果digit是0先执行x % 0这时候程序就已经出问题了。虽然有些环境上结果看起来还能跑但属于未定义行为换一台机器就可能是另一个结果。正确写法必须把digit 0放前面利用或运算的短路特性左边已经为true右边根本不会执行自然就避开了除零风险。4.3 第三个坑把“都满足”写成了“有一个就行”逻辑关系写反是另一类重灾区。我见过这样的代码if (digit ! 0 x % digit 0) { ok true; }问题很明确只要有一个数位满足条件ok就被设置成true下一个数位不满足时又会被改成false再下一个数位满足又变回true。最后一个数位决定最终结果整个判断毫无意义。正确写法是flag初始为true在循环内一旦发现不满足就置为false并break而不是反着来。一句话记忆找反例用“一旦发现就否定”找正例则是“全部通过才算数”。4.4 第四个坑循环边界和枚举区间细节如果你写的是区间枚举版本for (int i L; i R; i)里的等号很容易丢。丢了等号右端点R本身就不被检查如果R正好是优美数字答案就错一个数。同理while拆位循环的条件是tmp 0而不是tmp 0后者会导致tmp变成0后还在循环里取模死循环加除零双重事故。这种边界问题平时看着不起眼考场上紧张的时候特别容易手滑所以提交之前的检查清单里一定要有“所有边界等号是否齐全”这一项。4.5 第五个坑输出格式与题面要求题面要求输出大写YES和NO就千万别输出小写yes或no要求统计个数就别把每个优美数字也print出来要求每个数字一行就不要全都挤在一行。听起来像废话但每次考完都有同学在这些地方丢冤枉分。还有一个建议是程序写完以后把样例原封不动地输入一遍肉眼对比输出的每一个字符包括换行位置这是一个成本极低但收益极高的检查动作。5. 从这一题看GESP二级的备考方向5.1 二级编程题常见的四类题眼GESP二级的编程题不会超出一个入门者用循环和分支能解决的范围归纳起来大概四类。第一类是数字性质题比如判断素数、判断回文数、判断水仙花数、统计约数个数核心都是拆位和取模。第二类是累加累乘问题典型场景是求1到n的平方和、阶乘、多项式求和。第三类是图形输出题用嵌套循环打印三角形、菱形、矩形边框考察对循环层数的把控。第四类是简单数据处理题比如求一组数的最大值、最小值、平均值或者做简单的筛选统计。今天这道“优美的数字”就属于第一类而且是把拆位、取模、逻辑判断融合到一道题里的综合考察。把这道题吃透对备考二级的意义不只是会做这一道题。你会发现判断素数也用到x % i回文数也用到拆位水仙花数更是一模一样的拆位再加立方和判断。数字性质题的技术栈高度重合练会一道等于打通了一整片。5.2 用变体题训练“举一反三”备考阶段我建议把同类型题自己做一遍变体而不是只记一套模板。比如把本题定义改成“各位数字之和为素数”那核心代码就从“拆位后逐个判断整除”变成“拆位后累加求和再判断素数”。拆位部分完全保留只需要把验证逻辑换成写一个isPrime函数。再比如把定义改成“数字中不出现重复数字”那就需要用一个长度为10的布尔数组记录每个数字是否出现过出现重复就淘汰。这两种变体在二级模拟卷里都出现过底层技能和这道题完全互通。动手练变体的时候有一个很实用的方法把已经AC的原题代码复制出来只修改判断函数那一小段然后自己构造测试数据验证。这样能够在最短时间内体验到“逻辑改动”和“逻辑复用”的区别比反复做全新题目更能提升代码修改能力。考场上的原题往往就是平时练习题的组合或变体提前适应这种变化很占优势。5.3 考试时间的合理分配二级机考的时间通常是比较充裕的但每年都有学生因为前面选择判断磨蹭太久最后编程题草草提交。我的建议是拿到试卷先花两三分钟通读一遍所有题目把编程题在脑子里过一遍确定哪道题最容易、哪道题最耗时先做有把握的题把稳拿的分收进兜里再回头啃难题。像“优美的数字”这种中等偏易的题目读题加写代码加测试15分钟以内就应该全部搞定。写完代码后的检查环节别只跑题目给的样例。样例只能证明你的代码没有语法错误不能证明逻辑正确。我会额外测试几个刁钻输入1最小正整数、10含0、11所有位都相同、128教科书级优美数、999999接近上限的值。尤其要测与题目给出样例不同的值因为很多错误代码恰好能蒙对样例却在隐藏数据上翻车。我个人在实际带学生备考时最常说的一句话是这类数字性质题真正卡人的不是算法而是细心。写完代码后拿样例手推一遍再额外测几个刁钻数字往往比你多背十道题模板都管用。这道“优美的数字”如果按照拆位、判0、判整除的顺序一步步来稳住心态满分拿下并不难。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →