PrepStack · Markdown HTML

Digit dynamic programming

Dynamic programming · HTML

Formal shape

solve(pos,tight,started,summary) counts valid completions. The digit ranges from 0 to digits[pos] if tight, otherwise base-1; next_tight is tight and equality with the bound digit. Answer [L,R] by F(R)-F(L-1).

Recognition signals

False friends

SignalWhy it is different
Inspecting one given numberA simple digit loop suffices; no state reuse is needed.
A property requiring arbitrary full historyIf history cannot be compressed into a small summary, state size explodes.
No bound and a tiny fixed lengthDirect combinatorics or a simple recurrence may be clearer.

Core invariant

All prefixes sharing pos/tight/started/summary have exactly the same valid suffix set; tight precisely records whether the prefix still equals the upper-bound prefix.

State, transition, and processing order

ItemDefinition
StatePosition, tight flag, started flag, and the minimal property summary such as remainder, sum, count, or previous digit.
TransitionEnumerate each legal digit, update started and summary, and keep tight only when the old state was tight and the chosen digit equals the bound digit.
Frontier / orderDFS or iterative layers from most significant to least; cache either all states including tight or only the reusable non-tight states.

Python skeleton

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)

Correctness sketch

  1. Every integer from 0 through N has one fixed-length leading-zero digit path.
  2. The tight update excludes exactly the prefixes that first exceed N while retaining every bounded path.
  3. The summary updates deterministically and the terminal test accepts exactly the property; distinct paths represent distinct integers.

Complexity

TimeSpaceParameters
O(D·B·|Summary|·2), up to transition constantsO(D·|Summary|·2)D is digit count, B the base, and Summary the property-state domain.

Edge cases

Error patterns

PatternWhy it fails / fix
Updating tight against the wrong valueUse old_tight and equality with the actual bound digit at pos; do not improvise from a reused limit.
Treating leading zeros as real digitsOccurrence and adjacency properties need started to distinguish padding.
Mishandling L=0 in range subtractionDefine F(n)=0 for n<0, then always use F(R)-F(L-1).
Omitting influential data from the cache keyEvery quantity that changes legal suffixes belongs in state; bound digits must stay fixed within one call.

Selection and exclusion rules

RelationTemplateBoundary
state-featureModular arithmetic and fast powerThe summary maintains the current number modulo m.
compositionFinite-state-machine DPThe summary is a finite automaton driven by each digit.

Original micro-example

PartDetail
ResultThe count is 3, with 22 retained by the tight boundary.
SetupCount integers from 0 through 25 whose digit sum is 4.
TraceThe valid paths are 4, 13, and 22; under tight prefix 2, the last digit is capped at 5.

Recall prompts

My notes