Count (or sum, or optimise) over integers in (up to or given as strings of digits) satisfying a property computed digit by digit. Answer where handles . Build the number from the most significant digit with a tight flag (prefix equals ‘s prefix so far), a started flag when leading zeros matter, and whatever small state the property needs.

Template

Memoise only non-tight states and index them by the number of remaining digits: then the table does not depend on and is reused across all queries and test cases.

int K; ll memo[20][200]; bool vis[20][200]; string s;     // property: digit sum divisible by K
ll go(int pos, int rem, bool tight) {
    if (pos == sz(s)) return rem == 0;
    int left = sz(s) - pos;
    if (!tight && vis[left][rem]) return memo[left][rem];
    ll r = 0; int hi = tight ? s[pos] - '0' : 9;
    for (int d = 0; d <= hi; d++) r += go(pos + 1, (rem + d) % K, tight && d == hi);
    if (!tight) vis[left][rem] = 1, memo[left][rem] = r;
    return r;
}
ll countUpTo(ll N) { if (N < 0) return 0; s = to_string(N); return go(0, 0, true); }   // counts x in [0, N]

The extra state

PropertyState
digit sum / divisible by running sum (at most distinct values for 18-digit numbers) or remainder
number divisible by (append digit: )
divisible by its own digit sumloop over target sum ; state (sum, )
no two equal adjacent digits, monotone digitsprevious digit (+ started)
all digits distinct / digit multiset conditions10-bit mask, or counts
avoid/contain substrings from a setAho–Corasick state (Aho–Corasick)
number of nonzero digits counter
palindromes in fix the first half and compare the mirrored number with
lucky (divisible by each nonzero digit) and the lcm of digits so far (48 divisors of 2520)

Sums instead of counts: return (count, sum) and combine . Sums of squares or of follow by carrying the needed power sums.

-th number with the property: is monotone, so binary search on ( digit DPs), or descend directly: at each position, try digits in increasing order, subtracting the number of completions of each free suffix until the -th falls inside one.

Several numbers at once

Process bits (or digits) of all numbers together, with one tight flag per bound. Lower bounds get their own flag (“still equal to ‘s prefix”).

ll pairsDisjoint(ll A, ll B) {                             // # (a, b), 0 <= a <= A, 0 <= b <= B, a & b == 0
    ll dp[2][2] = {{0, 0}, {0, 1}};                         // dp[tightA][tightB]
    for (int bit = 61; bit >= 0; bit--) {
        ll nd[2][2] = {}; int ab = A >> bit & 1, bb = B >> bit & 1;
        for (int ta = 0; ta < 2; ta++) for (int tb = 0; tb < 2; tb++) if (dp[ta][tb])
            for (int x = 0; x < 2; x++) for (int y = 0; y < 2; y++) {
                if ((x & y) || (ta && x > ab) || (tb && y > bb)) continue;
                nd[ta && x == ab][tb && y == bb] += dp[ta][tb];
            }
        memcpy(dp, nd, sizeof dp);
    }
    return dp[0][0] + dp[0][1] + dp[1][0] + dp[1][1];
}

Typical: pairs with or , , , maximum of over ranges, ” and both satisfy P”.

Addition with carries: process from the least significant digit with a carry state, e.g. count such that has digit sum ; or verify digit-wise for cryptarithm-like problems. When both a carry (LSB first) and a bound (MSB first) are needed, either process LSB first and replace “tight” by a three-way comparison flag (less/equal/greater than ‘s suffix), or process MSB first with a “carry expected from below” guess.

Practical notes

  • Digit DP on decimal strings of length is fine as long as the state is small; iterate instead of recursing.
  • With leading zeros, properties about “the digits of ” need started; sums and remainders usually don’t.
  • Base or binary: same template. For bitwise problems, digit DP over bits is often combined with a binary trie view.
  • For ” in ” with two flags (tight-low, tight-high) you avoid the subtraction, which helps when the answer is a min/max rather than a count.