C · International Grandmaster · 14 min

Digit DP

Count numbers ≤ n whose digits satisfy a property. Walk digits left to right; the state is (position, tight, leftover flags). n can be 10^{18} because you only touch 18 positions.

How many integers in [0, n] do not contain the digit 4? How many have digit sum k? n is up to 10^{18} — you cannot loop. You walk the decimal representation.

State: pos (which digit you are filling), tight (whether the prefix still matches n, so the next digit is capped), and the property so far (seen a 4, digit sum, last digit, …). If tight is false, remaining digits are free 0..9.

count(r) - count(l-1) gives a range. CSES Counting Numbers. This is the usual CF 1800 'how many numbers' problem.

Count x in [0, n] with no digit 4js
function countWithoutFour(n) {
  if (n < 0) return 0;
  const digits = String(n).split("").map(Number);
  const memo = new Map();
  function dp(pos, tight, started) {
    if (pos === digits.length) return 1;
    const key = pos + "," + tight + "," + started;
    if (memo.has(key)) return memo.get(key);
    const cap = tight ? digits[pos] : 9;
    let ways = 0;
    for (let d = 0; d <= cap; d++) {
      if (d === 4) continue;
      ways += dp(pos + 1, tight && d === cap, started || d > 0);
    }
    memo.set(key, ways);
    return ways;
  }
  return dp(0, true, false);
}

TRACE

n = 25. Count numbers 0..25 with no digit 4 (include 0).

Tens digit

tight. d=0,1,2 allowed (4 would be skipped anyway). d=2 stays tight.

1 / 4

CHECK

Why is tight a boolean in the state, not 'the remaining number'?

CHECK

count(25) - count(14) for 'no digit 4'. Off-by-one?

Checks 0/2

Next lesson