PrepStack · Markdown HTML

数位 DP

动态规划 · HTML

数学 / 逻辑形态

solve(pos,tight,started,summary) 返回从当前位置填完的方案数。digit 范围 0..limit,其中 limit=tight?digits[pos]:base-1;next_tight=tight and digit==limit_digit。区间 [L,R] 用 F(R)-F(L-1)。

识别信号

容易误判的相似信号

SignalWhy it is different
只需计算一个给定数的数位性质直接循环拆位即可,不需 DP。
性质依赖完整任意历史若无法压缩成小 summary,状态会爆炸。
无上界且长度很短普通组合计数或矩阵递推可能更直接。

核心不变量

memo 状态代表所有具有同一 pos/tight/started/summary 的前缀,它们的合法后缀集合完全相同;tight 准确记录是否仍贴住上界前缀。

状态、转移与处理顺序

ItemDefinition
Statepos、tight、started,以及性质所需 summary(余数、和、计数、上一位等)。
Transition枚举当前合法 digit,更新 started/summary;只有当前仍 tight 且 digit 等于上界位时保持 tight。
Frontier / order从最高位向最低位的 DFS/迭代层;常只 memo tight=False 状态或将 tight 纳入缓存。

Python 骨架

from functools import lru_cache

def count_digit_sum_at_most(n, target):
    if n < 0: return 0
    digits = list(map(int, str(n)))
    @lru_cache(None)
    def dfs(pos, tight, total):
        if total > target: return 0
        if pos == len(digits): return int(total == target)
        limit = digits[pos] if tight else 9
        ans = 0
        for d in range(limit + 1):
            ans += dfs(pos + 1, tight and d == limit, total + d)
        return ans
    return dfs(0, True, 0)

正确性思路

  1. 每个 0..N 的定长补零表示对应唯一数位选择路径。
  2. tight 规则恰好排除首个超过上界的前缀,保留所有不超过上界的路径。
  3. summary 按位更新且终点只接受目标性质;互异路径对应互异整数,计数无重漏。

复杂度

TimeSpaceParameters
O(D·B·|Summary|·2),常数依转移O(D·|Summary|·2)D 为位数,B 为进制,Summary 为统计状态规模。

边界条件

错误模式

PatternWhy it fails / fix
next_tight 比较错对象必须是 tight and ddigits[pos],不是 dlimit 在所有语境下随意写。
前导零当真实数位统计出现次数/相邻性质时需 started 区分未开始前缀。
区间减法漏 L=0定义 F(n<0)=0,统一 F(R)-F(L-1)。
缓存包含可变外部状态影响后缀的量都必须进入 key;与上界相关的 digits 应固定在一次调用内。

选择与排除规则

相关模板

RelationTemplateBoundary
state-feature模运算与快速幂summary 维护当前数值模 m。
composition有限状态机 DPsummary 是由上一数位驱动的有限自动机状态。

原创微型例子

环节内容
Result计数为 3,22 通过 tight 边界保留。
Setup统计 0..25 中数位和为 4 的整数。
Trace合法路径对应 4、13、22;前缀 2 tight 时末位最多 5。

Recall prompts

我的补充