E - バランスチェック / Balance Check Editorial by admin
Claude 4.6 Opus (Thinking)Overview
This problem asks us to count positive integers from \(1\) to \(N\) where the absolute difference between the sum of digits at odd positions and the sum of digits at even positions is at most \(D\). We solve it using Digit DP.
Analysis
Problems with the Naive Approach
Since \(N\) can be up to \(10^{15}\), checking every integer from \(1\) to \(N\) one by one takes \(O(N)\) time, which is far too slow.
Key Insight
The problem of “counting positive integers up to \(N\) that satisfy a condition related to their digits” is a classic pattern for Digit DP. We determine digits one by one from the most significant digit, managing the following information as state:
- The current digit position being considered
- The current value of \(S_{\mathrm{odd}} - S_{\mathrm{even}}\) (tracking the difference)
- Whether we are still constrained by the corresponding digit of \(N\) (tight constraint)
- Whether the number has started (to ignore leading zeros)
- Whether the next digit to place is at an odd or even position (parity)
Tracking the Difference
Instead of managing \(S_{\mathrm{odd}}\) and \(S_{\mathrm{even}}\) separately, it suffices to track only the difference \(\mathrm{diff} = S_{\mathrm{odd}} - S_{\mathrm{even}}\). This is because we only need to check \(|\mathrm{diff}| \leq D\) at the end.
Since \(N\) has at most 16 digits, there are at most 8 odd-positioned digits and at most 8 even-positioned digits. Therefore, \(\mathrm{diff}\) is bounded within \([-72, 72]\).
Algorithm
We implement Digit DP using recursion with memoization.
- Convert \(N\) to a list of its decimal digits.
- Define a recursive function
dp(pos, diff, tight, started, parity):pos: The current digit position being determined (index in \(N\)’s digit sequence)diff: The current value of \(S_{\mathrm{odd}} - S_{\mathrm{even}}\) determined so fartight: Whether we are still bounded by \(N\)’s upper limit (if True, the next digit is restricted to at most the corresponding digit of \(N\))started: Whether valid digits have started yet (for handling leading zeros)parity: Whether the next digit to place is at an odd position (0) or even position (1)
- At each digit position, try all digits from \(0\) to the upper limit, updating
diffaccordingly:- If it’s an odd-positioned digit:
diff += d - If it’s an even-positioned digit:
diff -= d
- If it’s an odd-positioned digit:
- Pruning: Let \(r\) be the number of remaining digits. If \(|\mathrm{diff}| - 9r > D\), then it is impossible to satisfy the condition no matter what, so we skip.
- When all digits have been determined (
pos == n), return 1 ifstartedis true and \(|\mathrm{diff}| \leq D\).
Complexity
- Time complexity: \(O(n \times W \times 2 \times 2 \times 2 \times 10)\)
- \(n\): Number of digits (at most 16)
- \(W\): Number of possible values for diff (approximately 145)
- \(2 \times 2 \times 2\): Combinations of tight, started, parity
- \(10\): Number of digits tried at each position
- Total: approximately \(16 \times 145 \times 8 \times 10 \approx 185{,}600\) states × transitions
- Space complexity: \(O(n \times W \times 2 \times 2 \times 2)\) (memoization table)
Implementation Notes
Handling leading zeros: Managed with the
startedflag. While there are leading zeros, we consider the number as not having started yet, and do not updatedifforparity. When a non-zero digit first appears, it is always treated as an odd-positioned (1st) digit.Parity management: The “which position a digit is at” in a number depends on the length of the number itself, so it must be tracked based on the count since the number actually started, not based on the overall digit position including leading zeros.
Pruning: Speeds up computation by early elimination of states where recovery is impossible with the remaining digits.
Using
lru_cache: In Python,functools.lru_cacheallows for a concise implementation of memoized recursion.Source Code
import sys
from functools import lru_cache
def solve():
N = int(input())
D = int(input())
digits = list(map(int, str(N)))
n = len(digits)
# Digit DP
# State: position, diff (S_odd - S_even), tight, started
# diff can range from -9*8 = -72 to 9*8 = 72 for up to 16 digits
# Actually for 16 digits: max 8 odd positions * 9 = 72, similarly even
# diff range: -9*8 to 9*8 = -72 to 72
# But we need to handle up to 16 digits (10^15 has 16 digits)
# Max odd positions: 8, max even positions: 8 -> diff in [-72, 72]
# We'll use memoization with offset for diff
# diff = S_odd - S_even
# At position i (0-indexed), if i is even (0,2,4,...) it's odd-numbered (1st,3rd,...) -> add to S_odd
# if i is odd (1,3,5,...) it's even-numbered (2nd,4th,...) -> add to S_even
# But position indexing depends on the actual number's length when started
#
# Actually, we need to track the actual digit position within the number being formed.
# When the number hasn't started yet (leading zeros), we don't count those positions.
# So we need to track how many actual digits have been placed.
# State: pos in the template, number of actual digits placed so far, diff, tight
# number of actual digits: 0 to 16
# diff: -72 to 72 -> offset by 72, range 0 to 144
# Actually, let's think differently. We track pos, tight, started, and diff.
# When we place a non-zero digit and haven't started, we start. The first actual digit is at odd position (1st).
# After that, each subsequent digit alternates.
# Instead of tracking num_digits, we can track the parity of the next digit position.
# parity=0 means next digit goes to odd position (adds to diff), parity=1 means even position (subtracts from diff)
OFFSET = 130 # enough offset
MAX_DIFF = 260
@lru_cache(maxsize=None)
def dp(pos, diff, tight, started, parity):
# parity: 0 = next actual digit is odd-positioned, 1 = even-positioned
if pos == n:
if not started:
return 0
return 1 if abs(diff) <= D else 0
limit = digits[pos] if tight else 9
result = 0
for d in range(0, limit + 1):
new_tight = tight and (d == limit)
if not started and d == 0:
# Still not started
result += dp(pos + 1, 0, new_tight, False, 0)
else:
# Place digit d
new_started = True
if not started:
# First digit, it's at odd position (parity=0)
new_parity = 1 # next will be even position
new_diff = diff + d # odd position: add
else:
if parity == 0:
# odd position: add
new_diff = diff + d
else:
# even position: subtract
new_diff = diff - d
new_parity = 1 - parity
# Pruning: remaining positions
remaining = n - pos - 1
# max change possible is 9 * remaining
# if diff is already too far from 0, prune
if abs(new_diff) - 9 * remaining > D:
continue
result += dp(pos + 1, new_diff, new_tight, new_started, new_parity)
return result
print(dp(0, 0, True, False, 0))
solve()
This editorial was generated by claude4.6opus-thinking.
posted:
last update: