Official

E - スムーズな山道 / Smooth Mountain Path Editorial by kyopro_friends


この問題は桁DPにより解くことができます。

\(X\) 以下の正の整数であり「スムーズな山道」であるものの個数を \(f(X)\) とします。求める答えは、\(f(R)-f(L-1)\) ですが、\(L-1\) を計算するのは面倒なので「\(L\) がスムーズな山道であるとき \(f(R)-f(L)+1\) 、そうでないとき \(f(R)-f(L)\) 」と考えることにします。

\(L\) がスムーズな山道であるかどうかの判定は容易なので、 \(f(X)\) を高速に求めることができれば十分です。

下の桁から決めていく桁DPを考えます。

\(\mathrm{dp}_X[i][\mathrm{leq}][\mathrm{pre}][\mathrm{gap}]\) を、以下の条件を全て満たす長さ \(i\) の数字列 \(S\) の個数とします。

  • \(S\) を整数列と解釈したとき、階差は単調非減少である。
  • \(S\) の先頭の文字は \(\mathrm{pre}\) である。
  • \(S\) の先頭の文字と2番目の文字を、それぞれ整数と解釈したときの差の絶対値は \(\mathrm{gap}\) である。
  • \(S\) を整数と解釈したとき、\(X \bmod 10^i\) 以下なら \(\mathrm{leq}\) は True、そうでないなら False である

\(f(X)\)\(X\) の桁数を \(N\) として、

\(\sum\mathrm{dp}_X[< N][*][\neq 0][*]+\sum \mathrm{dp}_X[N][\mathrm{True}][\neq 0][*]\)

となります。DPの遷移は、\(S\) の先頭に追加する文字の種類を全て試せばよいです。

\(R\) の桁数を \(N\) 、基数を \(B\) として、計算量は \(O(NB^3)\) になります。

実装例 (C++)

#include<bits/stdc++.h>
#include<atcoder/modint>
using namespace std;
using mint=atcoder::modint1000000007;

mint f(string s){
  if(s.size() == 1){
    return s[0] - '0';
  }
  map<array<int, 3>, mint> dp;
  for(int i=0; i<10; i++){
    dp[{i <= s.back() - '0', i, 10}] = 1;
  }
  mint ans=0;
  for(int pos=s.size()-2; pos>=0; pos--){
    int c = s[pos] - '0';
    map<array<int, 3>, mint> new_dp;
    for(auto[key, val]: dp){
      auto[leq, pre, gap] = key;
      if(pre != 0){
        ans += val;
      }
      for(int i=0; i<10; i++){
        if(abs(i-pre) <= gap){
          array<int,3> new_key = {i<c || (i==c && leq), i, abs(i-pre)};
          new_dp[new_key] += val;
        }
      }
    }
    swap(dp, new_dp);
  }
  for(auto[key, val]: dp){
    auto[leq, pre, gap] = key;
    if(pre != 0 && leq){
      ans += val;
    }
  }
  return ans;
}

bool is_ok(string s){
  int gap=0;
  for(int i=1; i<s.size(); i++){
    int new_gap=abs(s[i-1]-s[i]);
    if(gap>new_gap)return false;
    gap=new_gap;
  }
  return true;
}

int main() {
  string L, R;
  cin >> L >> R;
  mint ans = f(R) - f(L);
  if(is_ok(L)) ans++;
  cout << ans.val() << endl;
}

Python では多倍長整数を扱うことができるため、\(L\) がスムーズな山道かどうを判定する代わりに、直接 \(f(R)-f(L-1)\) を計算しています。

実装例 (Python)

from collections import defaultdict
MOD = 10**9+7

def f(s):
  if len(s)==1:
    return int(s)
  dp = defaultdict(int)
  for i in range(10):
    dp[(i <= int(s[-1]), i, 10)]=1
  ans = 0
  for pos in range(len(s)-2,-1,-1):
    c = int(s[pos])
    new_dp = defaultdict(int)
    for (leq, pre, gap), val in dp.items():
      if pre != 0:
        ans += val
        ans += MOD
      for i in range(10):
        if abs(i-pre) <= gap:
          key=(i<c or (i==c and leq!=0), i, abs(i-pre))
          new_dp[key] += val
          new_dp[key] %= MOD
    dp = new_dp
  for (leq, pre, gap), val in dp.items():
    if pre != 0 and leq:
      ans += val
      ans %= MOD
  return ans

L = str(int(input())-1)
R = input()
print((f(R)-f(L)) % MOD)

posted:
last update: