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]Proof: tight flag, reuse, and
A number with the same length (pad with leading zeros) is characterised by the first position where its digit is smaller than ‘s, or by being equal to . While tight, the next digit can’t exceed ‘s digit; choosing a smaller one makes every later digit free; this enumerates each exactly once. A non-tight state’s count depends only on the remaining length and the property state, not on , so memoising by (digits left, state) is valid across different bounds. Finally .
The extra state
| Property | State |
|---|---|
| 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 sum | loop over target sum ; state (sum, ) |
| no two equal adjacent digits, monotone digits | previous digit (+ started) |
| all digits distinct / digit multiset conditions | 10-bit mask, or counts |
| avoid/contain substrings from a set | Aho–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.
Proof: LSB-first comparison flag
Process digits from the least significant end, keeping = comparison of the suffix built so far against the same suffix of . Prepending a digit against ‘s digit gives: if then , if then , else keep . This is exactly lexicographic comparison read from the most significant digit, since the most significant differing digit decides. At the end, iff the flag is or .
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.