公式

E - 数字の揺れ値 / Fluctuation Value of Digits 解説 by admin

claude4.8opus-high

Overview

This problem asks us to find the number of integers between \(1\) and \(N\) (inclusive) whose sum of the absolute differences between adjacent digits (referred to as the “fluctuation value”) is exactly \(K\). Since \(N\) can be as large as \(10^{18}\), which is extremely large, we solve this using digit DP (dynamic programming that determines digits from the most significant to the least significant).

Analysis

First, if we naively consider “checking all integers from \(1\) to \(N\) and calculating their fluctuation values,” since \(N\) is up to \(10^{18}\), the number of loop iterations would be enormous, leading to a Time Limit Exceeded (TLE).

The crucial observation here is that the fluctuation value is determined solely by the “differences between adjacent digits”. In other words, when we determine the digits of an integer from the most significant to the least significant, the only information we need to calculate the fluctuation value is the “digit placed immediately before” and the “accumulated fluctuation value so far”. We do not need to remember the exact sequence of digits placed before that.

By utilizing this property, we can represent integers up to \(N\) as a process of “determining digits one by one from the most significant digit,” and by grouping identical states together, we can significantly reduce the computational complexity. This is the core idea of digit DP.

Also, considering the upper bound of the fluctuation value \(K\), the absolute difference between any two adjacent digits is at most \(9\), and the number of digits is at most \(19\) (for \(10^{18}\)). Thus, the maximum fluctuation value is around \(9 \times (19-1) = 162\). The constraint \(K \leq 153\) fits within this range.

Algorithm

We treat \(N\) as a string and perform digit DP from the most significant digit to the least. We maintain the following states:

  • pos: Currently which digit we are deciding.
  • prev: The digit placed immediately before (used to calculate the difference for the fluctuation value).
  • ssum: The accumulated fluctuation value so far.
  • tight: Whether the prefix of the constructed number matches \(N\) exactly so far (if it matches, the next digit we can place is restricted to be at most the corresponding digit of \(N\)).
  • started: Whether the most significant digit (a non-zero digit) has already started (used to avoid counting leading zeros as “digits”).

At each digit, we try placing a digit \(d\) from \(0\) up to the upper bound and recursively move to the next digit.

Key transitions are as follows:

  • If we place \(d=0\) when the number has not started yet (started=False), we treat it as a “leading zero”, which does not count as a digit, and we do not add to the fluctuation value.
  • Once we place \(d \geq 1\) for the first time, the number starts (started=True), and from then on, we add the difference \(|prev - d|\) to the fluctuation value.

For example, for \(3517\), in the process of placing \(3 \to 5 \to 1 \to 7\), we add \(|3-5|\), \(|5-1|\), and \(|1-7|\) in order, obtaining a fluctuation value of \(12\).

Finally, when we have placed all digits (pos == n), we return \(1\) if the number has started (started=True) and the fluctuation value is exactly \(K\); otherwise, we return \(0\).

If the fluctuation value exceeds \(K\) along the way, it can no longer satisfy the condition, so we prune the search by returning \(0\).

Complexity

The number of states is \(\text{pos}\) (up to approx. \(19\)) \(\times\) \(\text{prev}\) (\(10\) possibilities) \(\times\) \(\text{ssum}\) (at most \(K+1 \leq 154\) possibilities) \(\times\) \(\text{tight}\) (\(2\) possibilities) \(\times\) \(\text{started}\) (\(2\) possibilities). Since there are \(10\) transitions from each state:

  • Time complexity: \(O(L \times 10 \times K \times 10) = O(L \cdot K)\) (where \(L\) is the number of digits, approx. \(19\))
  • Space complexity: \(O(L \times 10 \times K)\)

Both are sufficiently small, and the algorithm runs very fast.

Implementation Details

  • Memoization: We use @lru_cache to avoid recalculating the same states. Since the essence of digit DP is to group identical states, memoization is essential.

  • Handling of Leading 0s: By distinguishing the “number has not started yet” state with the started flag, we can correctly treat representations like 0007 as 7 (a \(1\)-digit number). Without this, the leading zeros would be treated as digits and included in the fluctuation value calculation, leading to incorrect results.

  • tight Flag: To ensure the constructed number does not exceed \(N\), as long as all the higher digits match \(N\) exactly, the next digit is restricted to be at most the digit of \(N\) at that position. Once we place a digit strictly smaller than the corresponding digit of \(N\), we can freely place any digit from \(0\) to \(9\) for the remaining positions.

  • Pruning: By aborting the search once ssum > K, we reduce unnecessary exploration.

    Source Code

import sys
from functools import lru_cache

def main():
    data = sys.stdin.readline().split()
    N = int(data[0])
    K = int(data[1])
    s = str(N)
    n = len(s)
    sys.setrecursionlimit(10000)

    @lru_cache(maxsize=None)
    def rec(pos, prev, ssum, tight, started):
        if ssum > K:
            return 0
        if pos == n:
            return 1 if (started and ssum == K) else 0
        limit = int(s[pos]) if tight else 9
        total = 0
        for d in range(0, limit + 1):
            ntight = tight and (d == limit)
            if not started:
                if d == 0:
                    total += rec(pos + 1, 0, 0, ntight, False)
                else:
                    total += rec(pos + 1, d, 0, ntight, True)
            else:
                nsum = ssum + abs(prev - d)
                total += rec(pos + 1, d, nsum, ntight, True)
        return total

    print(rec(0, 0, 0, True, False))

main()

This editorial was generated by claude4.8opus-high.

投稿日時:
最終更新: