拓冰建站拓冰建站
首页 / 资讯中心 / 正文

蓝桥杯2019省赛特别数的和:拆位法判断数字位含2/0/1/9

P8680这道题对新手来说就像一道“热身题”对老手来说更像是一道用来练手速的白送题。但它背后藏着一个高频考点如何优雅地判断一个数字的每一位上是否包含指定数字。这个考点在蓝桥杯省赛里年年变着花样出现从“特别数的和”到“不含4的数”“数字统计”本质都是同一套拆位逻辑。把这道题吃透等于白赚一个考点。1. 题目到底在问什么别被题干绕晕1.1 题干里的两个关键信息原题是蓝桥杯2019年省B组的一道填空题但出成了编程题。题干很短小明对数位中含有 2、0、1、9 的数字很感兴趣不包括前导 0在 1 到 n 中所有满足条件的数求这些数的和。注意“数位中含有”不是“等于”比如 102、291、90 都算而 123 不算因为不含2、0、1、9中的任意一个但 111 算。两个关键点决定了解题方向第一n 的范围是 1 到 10000。这个范围很小意味着不需要任何数学优化直接暴力枚举每个数逐位检查即可。如果 n 是 10^9 甚至 10^18那才需要数位DP。这道题卡在 10000就是告诉参赛者“我不为难你你老老实实一个个看就行。”第二题面明确“不包括前导 0”比如数字 1 在数位上看就是一位不能看作 0001 里有 0。所以判断时只处理实际存在的位也就是“除到 0 为止”。1.2 数据范围与时间预估暴力为什么是正确答案10000 个数每个数最多 5 位因为 10000 是 5 位数逐位判断每个数最多做 5 次除法取余。总运算量大概是 10000 × 5 50000 次这在任何语言里都是毫秒级完成。就算 n 上限真的给孩子放宽到 10^7也才 7000 万次运算C 和 Java 都能扛住Python 可能稍微紧一点但也能过。所以这道题的核心不是“如何优化”而是“如何正确地把拆位逻辑写对”。很多人不是不会做而是栽在循环边界和条件判定的细节上。2. 拆位判断的底层原理除法和取余是一对好搭档2.1 怎么取出一个数的每一位要判断 1234 里面有没有 2最直觉的办法是从个位开始一位一位剥下来。用数学语言描述对于任意正整数 xx % 10 得到的是个位数字x / 10 得到的是去掉个位后的数。循环执行这两步直到 x 变成 0就能把每一位都“扫”一遍。举一个例子x 2019第一步2019 % 10 92019 / 10 201确认个位是 9符合条件。第二步201 % 10 1201 / 10 20确认十位是 1。第三步20 % 10 020 / 10 2确认百位是 0。第四步2 % 10 22 / 10 0确认千位是 2并且循环结束。每一步都判断取出来的数字是不是 2、0、1、9 中的一个只要有一个符合这个数就是“特别数”累加到总和里。2.2 循环边界千万别写错为什么是 while(x) 而不是 for很多新手容易把拆位循环写成 for (int i 10; i 0; i / 10)但这样写必须知道数字的最大位数而且对 0 本身要单独处理。更稳妥的写法是 while (x 0) 或者 while (x ! 0)。特别注意题目求 1 到 n所以不会出现 x 0 的情况但如果你把判断逻辑写成单独的函数并且将来复用到判断 0 本身那就要小心。0 % 10 00 / 10 0while(0) 直接不进循环此时 0 这个数字本身其实是个“特别数”包含 0但因为它不在 1 到 n 的范围内不影响本题。不过这个边界容易让初学者困惑建议写注释说明。2.3 布尔标志位的使用习惯我需要一个变量来记录“这个数是不是特别数”。可以用布尔类型也可以用整型 flag。每检查一个数初始化为 false一旦发现某一位符合条件就置为 true然后立刻跳出循环因为已经确定这个数是特别数了不需要再看后面的位。这个“早退”的习惯很重要。虽然这道题不优化也不超时但如果你养成“能提前结束就提前结束”的思维遇到更复杂的枚举题时会少吃很多亏。3. 代码实现C、Python、Java 三版详解3.1 C 版本竞赛中最常用的写法#include iostream using namespace std; bool isSpecial(int x) { bool has false; while (x 0) { int digit x % 10; if (digit 2 || digit 0 || digit 1 || digit 9) { has true; break; } x / 10; } return has; } int main() { int n; cin n; long long sum 0; for (int i 1; i n; i) { if (isSpecial(i)) { sum i; } } cout sum endl; return 0; }这里有几个细节值得说sum 用 long long。n 最大 10000所有特别数加起来大概不会超过几千万int 其实够int 上限约 21 亿但我看到很多人在比赛中因为没想清楚数据范围把求和变量写成 int虽然这题侥幸没炸但万一把 n 改成 10^6 或者 10^7int 就会溢出。竞赛老手的原则是任何求和变量只要不是明确知道上限一律用 long long。这是一种肌肉记忆不是小题大做。判断条件写在函数里让主逻辑变得干净。你别小看这个习惯在考场上函数封装能让你快速排查 bug也能让你在复用代码时省力。cin / cout 在这题完全够用。有些资料建议加 ios::sync_with_stdio(false)对付大量 io 确实需要但本题只有一次输入一次输出加不加无所谓。不过作为一个习惯我建议加上因为蓝桥杯后续题目经常有大规模输入输出提前养成习惯能避免很多意外。3.2 Python 版本更贴近人类思维的写法def is_special(x): while x 0: digit x % 10 if digit in (2, 0, 1, 9): return True x // 10 return False n int(input()) total 0 for i in range(1, n 1): if is_special(i): total i print(total)Python 写起来是最直观的。注意 range(1, n 1) 的右边界是开区间这个坑很多 Python 入门者都踩过忘写 1 就少算一个数。另外digit in (2, 0, 1, 9)这个写法很 Pythonic性能没问题。还有另一种更 Python 的解法直接把数字转成字符串然后判断2 in str(i) or 0 in str(i) or 1 in str(i) or 9 in str(i)。这个写法虽然更简洁但我在竞赛实操里不推荐——万一题目改成“判断数字是否包含指定数字”是高频考点字符串转换在数字很大时会慢一些而且有些在线评测环境对字符串操作并没有想象中友好。更重要的是数位拆分的做法能迁移到更多题型比如“判断回文数”“求数字之和”等练拆位比调字符串更练内功。3.3 Java 版本注意循环习惯import java.util.Scanner; public class Main { private static boolean isSpecial(int x) { while (x 0) { int digit x % 10; if (digit 2 || digit 0 || digit 1 || digit 9) { return true; } x / 10; } return false; } public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); long sum 0; for (int i 1; i n; i) { if (isSpecial(i)) { sum i; } } System.out.println(sum); } }Java 版基本就是 C 的翻版。唯一要提醒的是蓝桥杯 Java 组提交时public class 的名称必须是 Main否则评测机会报编译错误。这个错误每年都有大量考生犯说实话很冤。3.4 代码运行验证以 n 40 为例我当年第一次做这题时为了验证逻辑手动枚举了前几个数1包含 1是特别数2包含 2是特别数3不包含不是4不是5不是6不是7不是8不是9包含 9是特别数10包含 1 和 0是特别数11包含 1是特别数12包含 1 和 2是特别数15包含 1是特别数当 n 40 时所有特别数之和是 1 2 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 39 40。注意3 到 8 之间只有 3、4、5、6、7、8 排除还有 33、34、35、36、37、38 也要排除。这个验证过程能帮助你确认条件写没写对。4. 常见问题与调试技巧这些坑我当年全踩过4.1 忘记处理 0 的位数题目说 1 到 n所以主循环里 i 从 1 开始这个问题不存在。但如果你把判断函数单独拿出来测试发现输入 0 时返回 false于是怀疑代码有问题。其实不是代码错是边界定义问题。蓝桥杯这类题几乎不会让你判断 0但你在自己练习时最好把函数注释写清楚该函数只用于正整数的数位判断。4.2 把x % 10和x / 10的顺序搞反先取余还是先除法道理很简单你先把个位拿走记录完再把它从原数上剝掉。如果先做除法后取余得到的是十位但原数已经被改了你能循环下去吗也能但逻辑容易混乱。我见过有人写while (x) { x / 10; int digit x % 10; }这样第一次循环和最后一次循环都会出问题特别是最后一位会被丢掉。这属于典型的手误调试方法很简单打印每一步的 x 和 digit一眼就能看出来。4.3 条件写成了连续判断导致逻辑错误有人贪图省事写出if (digit 2 || 0 || 1 || 9)这在 C/Java 里会编译报错吗不一定C 里0 || 1其实是先算(digit 2)再算0再算1最终整个或表达式永远为 true。意思就是你判断了等于 2但后面接的是“0”这个常量0 表示 false可1表示 true所以整个表达式恒为 true导致每个数都被当成特别数。Python 里写成if digit 2 or 0 or 1 or 9:同样坑因为digit 2是布尔值然后or 0会变成or False或or True最终恒真。这个错我曾经在给同学改代码时见到过三次务必写全。4.4 用字符串解法时别忘了字符类型用 Python 字符串判断时str(i)之后要用字符串2不是数字 2。我在网上看到很多人把if 2 in str(i)写成if 2 in i直接报类型错误。虽然语言会提示但在比赛中这种低级浪费时间很可惜。4.5 求和时未考虑大数前面提过long long 是竞赛底线。蓝桥杯评测时你就算用 int 也未必能发现因为 n 小。但你要知道很多暴力枚举题改个数据范围就是另一道题比如“2019 特别数的和”可以扩展到 n 10^9那时候位数可达 10 位特别数的数量级可能达到几万亿int 完全不够。养成用 long long 的习惯能让你少承担一次 submit 失败的心理打击。5. 举一反三从这题延伸出去的高频考点5.1 “数位统计类”的基础套路特别数的和核心是枚举 拆位判断。很多变体题只是把“包含 2、0、1、9”改成其他条件统计 1 到 n 中有多少个数字包含数字 7比如 7、17、27……。统计 1 到 n 中所有数字各个位数之和比如 12 的各位之和是 3。判断一个数是不是“不含 4 的数字”类似“幸运数”。这些题统统可以用相同的拆位模板解决枚举每个数while 循环里取余 除法把每一位或统计、或判条件。蓝桥杯题目“数字统计”“质数拆分”“平方和”等都脱胎于此。掌握了拆位你就掌握了一类题的钥匙。5.2 从暴力到数位 DP什么时候不能硬枚举如果 n 改成 10^18就绝对不能枚举。这时候要用数位 DP把“数字拆位”和“状态记忆化”结合。但学数位 DP 之前必须先把暴力拆位练到十分熟练。因为数位 DP 里的核心循环本质上也是在拆位只不过把枚举过程从“对所有整数循环”变成了“对每一位的取值循环”。我的建议是省赛阶段把这题和类似的暴力题吃透就够了。国赛冲刺阶段再补充数位 DP 模板。千万不要上来就学数位 DP那会打击自信心而且由于没有暴力题的基础根本理解不了为什么要记忆化。5.3 一道很好的练手变体2019 省 B 的另一题“数列求值”2019 省 B 还有一道题是给定一个序列要求输出第 n 项其中每项是前 3 项之和的末 4 位。那道题表面和特别数的和无关但核心也是模运算——你需要时刻把数据控制在 int 范围内取模可以让我们不存大数。这和本题判断数位时的% 10是同一类思路利用取余把问题规模降下来。所以多刷真题你会发现很多题目底层用的是同一个数学工具只是套了不同的壳。6. 刷题习惯建议如何让这类基础题变成稳拿分项6.1 先手算小数据验证逻辑我每次拿到一道枚举题不会直接写代码而是先找一个小 n比如 20 或 40用笔在草稿纸上列出所有满足条件的数算出和然后作为测试用例。这样写代码时心里有底调试也快。本题 n 40 时特别数之和是 1 2 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 39 40。手算一次之后代码一跑结果对不上立刻知道是自己逻辑问题对得上就能安心提交。6.2 用“输出中间变量”查 bug假设你写完了结果不对在代码里临时加上几个输出语句打印每次循环的 i、digit、has甚至打印每个特别数的值。这个土办法在比赛里最实用。别觉得掉价真正调试效率最高的往往就是 print 大法。用熟了以后你还能学会设置条件断点但蓝桥杯的现场环境不一定好使print 永远不会背叛你。6.3 尝试写多个版本的解我建议你用 C 写一遍再用 Python 写一遍。为什么因为 C 逼你考虑类型、循环、运算优先级Python 逼你注意缩进和 range 边界。当你用两种语言都能实现同一道题说明你真的理解了解题逻辑而不是只会抄模板。蓝桥杯允许选择语言你总得有一门语言能写到闭眼出代码。以我个人的经验C 是竞赛最稳的选择但 Python 在快速原型验证时非常有价值两者可以互为补充。7. 我在实际刷题中的一点体会这道题我刷过很多遍每次看到都有新的领悟。早年间我做这题时只想着套模板后来教别人发现最难讲清楚的就是“为什么要拆位”。于是我想了一个生活化的类比判断一个数里有没有某个数字就像在一串珍珠项链里找某颗颜色的珠子。项链太长不能一眼看完只能一颗一颗摸过去。摸到一颗就看看颜色然后摸下一颗。把整个项链摸完标记那串项链是不是特殊款式。所谓的下一颗就是除以 10 剥离掉最后一位所谓的摸就是取余 10 得到最后一位。这个比喻我给很多学生讲过无一例外他们都秒懂。如果你现在正准备蓝桥杯省赛请把这类题当作“热身跑”。它能帮你找回对数字、循环、条件判断的直觉。不要嫌它简单考场上真正的敌人不是不会做而是紧张导致写错边界、吃不准数据类型。多刷十几道这种“送分题”你就能在赛场上节省出宝贵的时间留给那些真正拉开差距的 DP 和深搜题。最后说一个小技巧比赛前把所有小体积题目集中刷十道形成肌肉记忆到了现场你甚至不用思考就能写完本题这本身就是一种实力。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门