dfs需带「前一位是否是4」和「是否已受限」两个状态,因为前者防止生成49(上位为4时当前不能填9),后者确保不超上限(如上限527时百位选5则十位≤2);缺一即导致计数错误或高估。

为什么 dfs 要带「前一位是否是4」和「是否已受限」两个状态?
不含连续 49 的本质约束是:只要上一位填了 4,当前位就不能填 9。所以必须记住「上一位是不是 4」——记作 last_is_4(bool)。同时数位 DP 通常从高位往低位填,过程中可能卡在原数某一位(比如上限是 527,填到百位选了 5,十位就只能 ≤ 2;若百位选了 4,十位就能自由填 0~9),这个「是否还能随便填」就是 limit 状态。漏掉任一状态都会导致计数错误。
常见错误:只记 last_digit(比如上一位的数值),看似更通用,但会把状态数从 O(len × 2 × 2) 暴涨到 O(len × 10 × 2),且对本题无必要;或者忽略 limit 直接全填 0~9,结果高估数量(比如算 [0, 100] 却按 [0, 999] 算)。
dp[pos][last_is_4][limit] 的维度和初始化怎么设?
假设最大数最多有 20 位(如 1e18),pos 表示当前处理第几位(从 0 开始或从高位开始都行,统一即可);last_is_4 是 0/1;limit 也是 0/1。三维数组大小为 dp[20][2][2],初始全填 -1 表示未计算。
注意:不能用全局数组反复 memset,因为多组测试用例时会残留旧值;推荐每次新查询前用 memset(dp, -1, sizeof dp) 或局部 vector 初始化。
边界判断写法示例:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
if (pos == digits.size()) return 1;
即填完所有位算一个合法数字(含前导零,但不影响计数,因为
0、4、49 等本身不触发非法,且我们统计的是「不含 49」的总数,0 是合法的)。递归中如何枚举当前位、跳过非法组合?
当前位可填数字范围由 limit 决定:若 limit == 1,上限是 digits[pos];否则是 9。然后对每个候选数字 d:
- 如果
last_is_4 && d == 9,跳过(避免形成49) - 否则,新状态的
last_is_4=(d == 4),新limit=limit && (d == digits[pos])
关键细节:前导零不视为「上一位是 4」。例如数字 0049 实际是 49,但你在填前两位 0 时,last_is_4 应保持 false,直到真正填到非零位。实现上,只要还没填过非零数字(即仍处于前导零阶段),就把 last_is_4 视为 false;一旦填了非零数,后续就按真实值更新。不过本题中,填 0 不会触发 49,所以直接让 last_is_4 在 d == 4 时置 true、否则保持原值即可——因为前导零本身不改变「上一位是否为 4」的事实(没上一位)。
如何处理区间 [L, R] 和前导零干扰?
标准做法是写一个 count(n) 函数返回 [0, n] 中不含 49 的数的个数,答案就是 count(R) - count(L-1)。注意 L 可能为 0,要防负数。
前导零不影响正确性:比如 n = 100,数位数组是 [1,0,0],dfs 过程中允许高位填 0,这对应实际数字如 7、42 等,它们天然不含 49,且不会因「上一位是 0」而误判——因为我们只关心「上一位是否为 4」,不是「上一位是什么」。所以无需额外标记「是否开始填数」,last_is_4 初始传 0 即可。
容易被忽略的点:当 n = 0 时,digits 是空或单元素 [0],需确保 dfs 能正确返回 1(0 合法);另外,记忆化数组的索引中 pos 是从高位还是低位开始,要和 digits 下标一致,否则越界或漏位。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










