公式

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 such that the sum of the absolute differences between adjacent digits (the fluctuation value) is exactly \(K\). Since \(N\) can be as large as \(10^{18}\), we solve this using Digit DP (dynamic programming that counts digit by digit).

Observations

Limitations of a Naive Approach

We can find the answer by checking all integers from \(1\) to \(N\) and calculating their fluctuation values, but this will easily TLE since \(N \leq 10^{18}\). We need to speed up the counting process in some way.

Key Insight

The fluctuation value is the cumulative sum of the absolute differences between adjacent digits when looking at each digit from the most significant digit (MSD) to the least significant digit (LSD). In other words, when we determine the digits one by one from the most significant digit:

  • If we know the digit placed immediately before, we can add the absolute difference between it and the next digit.

This property is highly compatible with “Digit DP,” which determines digits sequentially from the most significant to the least significant.

Furthermore, the constraint \(K \leq 153\) is also important. For an \(18\)-digit number, the maximum possible sum of absolute differences between adjacent digits is \(9 \times 17 = 153\). Thus, the possible range of the fluctuation value is within \(0\) to \(153\). Therefore, even if we maintain the cumulative sum of the fluctuation value as part of the DP state, the number of states remains sufficiently small.

Algorithm

We will use Digit DP. We treat \(N\) as a string and decide “which digit to place” sequentially from the most significant digit. We maintain the following states:

  • pos: The current digit position we are considering (from the most significant digit).
  • prev: The digit placed immediately before.
  • started: A boolean/flag indicating whether we have already placed the most significant digit (a non-zero digit), which is used to handle leading zeros.
  • sum: The cumulative sum of the fluctuation value up to the current digit.
  • tight: A boolean/flag indicating whether the digits placed so far exactly match the prefix of \(N\) (if true, the next digit has an upper limit).

State Transitions

We iterate the digit d to be placed at the current position from \(0\) up to its upper limit.

  • If the number has not started yet (started=0):
    • If we place d=0, we remain in the “not started” state (ignoring the leading zero).
    • If we place d≠0, this becomes the most significant digit. We transition to started=1, and the fluctuation value is still \(0\).
  • If the number has already started (started=1):
    • We add |prev - d| to the cumulative fluctuation value and proceed to the next digit.

Handling the tight Flag

When tight=true, the digit we can place at the current position is restricted up to the corresponding digit of \(N\). The next state will have tight=true if and only if the current tight is true and the chosen digit d is equal to this upper limit.

Termination Condition

Once we have placed all digits, we return \(1\) if “the number has started (started=1) and the cumulative sum of the fluctuation value is equal to \(K\)”; otherwise, we return \(0\).

Memoization

States with tight=false (the free states not restricted by \(N\)) appear repeatedly, so we avoid redundant calculations by memoizing them in a memo array. States with tight=true occur at most once per digit position, so we do not memoize them.

Complexity

Let \(L = O(\log N)\) be the number of digits, and \(S = O(\log N \times 9)\) be the maximum possible fluctuation value.

  • Time Complexity: \(O(L \times 10 \times 2 \times S \times 10) = O(\log^2 N)\)
    • This is because there are \(L \times 10 \times 2 \times S\) states, and for each state, we transition by trying up to \(10\) possible digits.
  • Space Complexity: \(O(L \times 10 \times 2 \times S) = O(\log^2 N)\)

Both are sufficiently fast even for \(N \leq 10^{18}\).

Key Implementation Points

  • Handling Leading Zeros: If we treat a number like \(35\) as \(00035\) with leading zeros, we would incorrectly count the differences between digits that do not actually exist. We manage whether the number has actually started using the started flag, ensuring that zeros placed before the number starts do not affect the fluctuation value.

  • Capping the Fluctuation Value: If sum becomes too large, it may cause out-of-bounds access in the memoization array. In the code, if the cumulative sum reaches \(200\) or more, we clamp it to \(199\). Since \(K \leq 153\), once the sum reaches \(200\) or more, it can never equal \(K\) anyway. Treating all values \(\ge 200\) as a single state does not affect the answer, and it helps keep the array size small.

  • Memoization Condition: Note that we should not memoize states where tight=true. Because the upper limit of the transition depends on the tight flag, the results for the same (pos, prev, started, sum) can differ based on whether tight is true or false. By memoizing only the free states (tight=false), we can achieve correct and fast execution.

    Source Code

#include <bits/stdc++.h>
using namespace std;

string s;
int n;
long long K;
long long memo[20][10][2][200];
bool vis[20][10][2][200];

long long rec(int pos, int prev, int started, int sum, bool tight){
    if(pos==n){
        return (started && sum==K)?1LL:0LL;
    }
    if(!tight && vis[pos][prev][started][sum]) return memo[pos][prev][started][sum];
    int limit = tight ? (s[pos]-'0') : 9;
    long long res=0;
    for(int d=0; d<=limit; d++){
        bool nt = tight && (d==limit);
        if(!started){
            if(d==0){
                res += rec(pos+1, 0, 0, 0, nt);
            } else {
                res += rec(pos+1, d, 1, 0, nt);
            }
        } else {
            int ns = sum + abs(prev-d);
            if(ns>=200) ns=199; // cap, won't equal K (K<=153)
            res += rec(pos+1, d, 1, ns, nt);
        }
    }
    if(!tight){
        vis[pos][prev][started][sum]=true;
        memo[pos][prev][started][sum]=res;
    }
    return res;
}

int main(){
    long long N;
    cin >> N >> K;
    s = to_string(N);
    n = s.size();
    memset(vis,0,sizeof(vis));
    long long ans = rec(0,0,0,0,true);
    cout << ans << "\n";
    return 0;
}

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

投稿日時:
最終更新: