問題
正の整数 $N$ が与えられる。$1$ 以上 $N$ 以下の整数 $x$ のうち、$x$ が $x$ の各桁の和で割り切れる ものの個数を求めよ(10 進表記。例: $12$ は桁和 $3$ で割り切れる、$11$ は桁和 $2$ で割り切れない)。
入力形式
N
制約
| # | 条件 |
|---|---|
| 1 | $1\le N\le 10^{14}$ |
| 2 | $N$ は整数 |
入出力例
入力例1
20出力例1
13($1,\dots,9$ の 9 個と $10,12,18,20$ の 4 個)
入力例2
100出力例2
33($100$ 自身も桁和 $1$ で割り切れるので数える。「$N$ 自身」を数え忘れる実装は $32$ と答える)
入力例3
100000000000000出力例3
3902487368735背景と出典
AtCoder Beginner Contest 336 E「Digit Sum Divisible」(2024-01-14)と同じ設定。本セッションでは問題ページ・公式解説を開いておらず、出力例 3 は本解で計算した値である。正しさの根拠は下の検証ログ($10^5$ までの全数と、$10^7$ までの 5 点で素朴な数え上げと一致)。
桁 DP は「$N$ 以下の整数の数え上げ」を上の桁から 1 桁ずつ決める定番(Day 103 Q2 で Aho–Corasick と組み合わせた)。本問は 割る数が $x$ に依存する ので、そのままでは余りの状態が持てない点が新しい。
概念図:桁和固定の桁 DP
ヒント(クリックで段階的に開示)
ヒント1(方向性)
$x\bmod 7$ のように割る数が決まっていれば、上の桁から「余り」を持つ桁 DP で数えられる。割る数が桁和(最後まで分からない)なのが困る点。
ヒント2(アプローチ)
桁和は高々 $9L$($L$ は $N$ の桁数。$N=10^{14}$ は 15 桁なので 135 まで調べれば足りる。実際に現れる最大は $99\,999\,999\,999\,999$ の 126)。桁和 $s$ を 1 つ決め打ち して、「桁和がちょうど $s$、かつ $x\equiv0\pmod s$」の $x$ を数え、$s=1,\dots,9L$ で足す($L$ は $N$ の桁数)。
ヒント3(誘導)
$s$ を固定したとき、状態は (いまの桁和 $r\le s$、いまの値 $\bmod s$、$N$ と先頭が一致しているか)。
1 桁 $d$ を足すと $r\to r+d$、余り $m\to(10m+d)\bmod s$。最後に $r=s$ かつ $m=0$ の個数が答え。先頭一致の状態は 1 通りしか無いので、DP 表とは別に $(ts, tm)$ として持つと速い。
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
from operator import add
def count_for(digits, s):
"""[0, N] のうち 桁和 = s かつ s で割り切れる数の個数"""
L = len(digits)
# dp[r][m]: N より確実に小さいと決まった状態で、桁和が r、s で割った余りが m の個数(r <= s)
dp = [[0] * s for _ in range(s + 1)]
ts, tm = 0, 0 # N と先頭が一致している唯一の状態の (桁和, 余り)
shift10 = [(10 * m) % s for m in range(s)]
for i in range(L):
nd = digits[i]
# 1) 余り m → 10m(桁を 1 つずらす)を先に済ませる
base = [[0] * s for _ in range(s + 1)]
for r in range(s + 1):
row = dp[r]
if not any(row):
continue
b = base[r]
for m in range(s):
v = row[m]
if v:
b[shift10[m]] += v
# 2) 次の桁 d を置く: 桁和 r → r+d、余り m → m+d(リストの回転+要素ごとの和)
new = [[0] * s for _ in range(s + 1)]
lo = s - 9 * (L - 1 - i) # 残りの桁を全部 9 にしても s に届かない桁和は捨てる
for d in range(10):
for r in range(max(0, lo - d), s + 1 - d):
b = base[r]
if not any(b):
continue
dd = d % s
rot = b[-dd:] + b[:-dd] if dd else b
new[r + d] = list(map(add, new[r + d], rot))
# 3) 先頭一致の状態から N の桁より小さい d を置くと「確実に小さい」側へ移る
if ts <= s:
for d in range(nd):
if lo <= ts + d <= s:
new[ts + d][(tm * 10 + d) % s] += 1
ts += nd
tm = (tm * 10 + nd) % s
dp = new
res = dp[s][0]
if ts == s and tm == 0:
res += 1 # N 自身
return res
def main():
n = int(sys.stdin.readline())
digits = list(map(int, str(n)))
print(sum(count_for(digits, s) for s in range(1, 9 * len(digits) + 1)))
main()
計算量: 桁和の上限 $S=9L$ として $O(L\cdot 10\cdot\sum_{s\le S}s^2)=O(10L\cdot S^3/3)$。枝刈り(Step 4)で実際はこれより小さい。実測(Python 3.14.7、M1 Mac)で $N=10^{14}$ が 1.16 s、$N=10^{15}$ が 1.52 s。
Step-by-Step 解説
「$x$ が桁和で割り切れる」は、「ある $s$ について、桁和が $s$ かつ $x\equiv0\pmod s$」と同じ。$s$ ごとに条件が排他的(桁和は 1 つに決まる)なので、$s$ ごとに数えて足せば重複も漏れもない。
上の桁から 1 桁ずつ置く。いまの値を $v$ とすると、次の桁 $d$ を置いた値は $10v+d$ なので、余りは $m\to(10m+d)\bmod s$、桁和は $r\to r+d$。$r>s$ になった状態は二度と $s$ に戻らない(桁和は減らない)ので捨てる。
$N$ と先頭が一致している状態は常に 1 通り($N$ の先頭そのもの)。そこから $N$ の桁より小さい数字を置いた瞬間に「$N$ より確実に小さい」側へ移り、以後は 0〜9 を自由に置ける。先頭に 0 を置くと桁数の少ない数を表す(0 は桁和にも余りにも影響しない)。最後に先頭一致の状態=$N$ 自身を数え忘れないこと(入力例 2)。
$i$ 桁目を置いたあと残りは $L-1-i$ 桁で、全部 9 でも $9(L-1-i)$ しか増えない。$r<s-9(L-1-i)$ の状態は最後に $s$ に届かないので計算しない。大きい $s$ では前半の桁で状態の大半が消える。$N=10^{14}$ で枝刈りなし 2.80 s → あり 1.16 s(実測)。
余りの遷移 $m\to10m+d$ を「$m\to10m$(1 回の並べ替え)」と「$m\to m+d$(リストの回転)」に分け、回転したリスト同士の足し算を map(add, …) で C 側に任せた。内側の Python ループは「$m\to10m$」の 1 回だけになる。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| $1..N$ を全部調べる | $O(N\log N)$ | 検算用のみ | $N=10^{14}$ では不可能。$10^7$ までの検算に使った |
| 状態に「桁和」と「$x$ そのものの $\bmod\ \mathrm{lcm}(1..126)$」を持つ | lcm が巨大 | 不採用 | $\mathrm{lcm}(1,\dots,126)$ は 50 桁を超え($\approx e^{126}$、導出)、状態が持てない |
| 各 $s$ で 4 次元の素朴 DP(Python の 3 重ループ) | $O(10L\sum s^2)$ を Python で | 不採用 | 見積もり約 1.3 億回の Python 演算で数十秒。本解は同じ計算量の大部分を map に逃がした |
| 枝刈りなしの本解 | 同上 | 不採用 | $N=10^{14}$ で 2.80 s(実測)。枝刈りで 1.16 s |
| NumPy で $(r,m)$ の表をまとめて動かす | 同上 | 保留 | AtCoder の Python には NumPy があるが、本セッションの環境に無く測っていない |
| $s$ 固定の桁 DP+枝刈り(本解) | 上記 | 採用 | $10^5$ まで全数一致・$10^7$ まで 5 点一致 |
前提が変わったら:
- $N\le10^{18}$ なら $S=162$ で約 2.5 倍重くなる見積もり($S^3$ に比例、導出)。PyPy か NumPy を使う
- 「桁和の $\bmod$」ではなく「桁の積で割り切れる」 なら、積は $2^a3^b5^c7^d$ の形に限られるので、指数の組を状態にする別の DP になる
- $[L,R]$ の区間で聞く なら $f(R)-f(L-1)$ で済む(本解の関数を 2 回呼ぶ)
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| $N$ 自身を数え忘れる | 「確実に小さい」側しか最後に見ない | 最後に先頭一致の状態 $(ts,tm)$ も判定(検算 503 件中 92 件で誤り) |
| 余りの状態を $x\bmod(\text{いまの桁和})$ にする | 割る数が途中で変わるのに余りを持ち越す | 割る数 $s$ を外側で固定する |
| $s$ の上限を $9\times(\text{桁数}-1)$ にする | $N$ の桁数を数え間違える | $9L$($L=$ len(str(N))) |
| 桁和が $s$ を超えた状態を残す | 捨て忘れ | $r+d\le s$ の範囲だけ遷移 |
次のステップ
- 発展1: 「桁和で割り切れる数」の $K$ 番目を求める(答えで二分探索して本解を $\log$ 回呼ぶ、または桁 DP の表を使って上の桁から決める)
- 発展2: 16 進表記など底 $B$ で同じ問題を解く。桁和の上限と計算量がどう変わるかを見積もってから実装する
- 次回予告: 焦点帯 M1 を続ける
検証ログ(本セッションで実行した数値)
- 素朴な数え上げとの一致(実測): $N=1..199$ 全部と $[1,10^5]$ の乱数 300 点、$N=9999,10000,99999,10^5$ の計 503 件で全件一致。さらに $N=10^6, 1234567, 5555555, 9876543, 10^7$ の 5 点で $1..N$ の全数調べと一致(例: $10^7$ で 806095)
- $N$ 自身を数え忘れた版(実測): 503 件中 92 件で誤り(誤るのは $N$ 自身が条件を満たす場合に限られる)
- 実行時間(実測): 枝刈りあり $N=10^{12}$ 0.63 s・$10^{14}$ 1.16 s・$10^{15}$ 1.52 s。枝刈りなしは $10^{14}$ 2.80 s・$10^{15}$ 3.87 s
- 扱っていない範囲: ABC336 E の公式の入出力例・解説との照合、NumPy 版の計測、PyPy での計測