Day 163-Q2 — 2026-09-28 — 桁和で割り切れる数の個数(桁 DP。割る数=桁和が最後まで決まらないので、桁和 s を外側で固定して s 回 DP する)

2026-09-28 赤色 Master (Phase 8+) ★★★★★★★★★ 桁 DP(焦点帯 M1)。「$x\bmod(\text{桁和})$」は、上の桁から読むあいだ割る数が分からない。答えの側(桁和 $s$)を先に決め打ちして状態を $(\text{桁和},x\bmod s)$ に閉じ込める「外側で決め打つ」型

問題

正の整数 $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

外側で桁和 s を固定 → 状態は (いまの桁和 r ≤ s, いまの値 mod s)。例: s=3 余り m = 値 mod 3 m=0 m=1 m=2 r=0 r=1 r=2 r=3 答え 例 d=2: (r,m)=(1,1) → (3, (10·1+2) mod 3) = (3,0) 1 桁 d を置く遷移 r → r + d(s を超えたら捨てる) m → (10·m + d) mod s Python での分解 ① m → 10m mod s(並べ替え 1 回) ② m → m + d(リストの回転+map(add)) 枝刈り r < s − 9×(残り桁数) は s に届かない N=10^14 で 2.80 s → 1.16 s(実測) N と先頭が一致する状態は常に 1 通り → 表とは別に (ts, tm) で持ち、N の桁より小さい d を置いたら表へ移す 最後に表の (r=s, m=0) +「N 自身」が条件を満たすなら 1 を足す(入力例 2 の 100) s = 1 .. 9×桁数 で合計。桁和は 1 つに決まるので重複しない

ヒント(クリックで段階的に開示)

ヒント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 解説

1割る数を外に出す

「$x$ が桁和で割り切れる」は、「ある $s$ について、桁和が $s$ かつ $x\equiv0\pmod s$」と同じ。$s$ ごとに条件が排他的(桁和は 1 つに決まる)なので、$s$ ごとに数えて足せば重複も漏れもない。

2$s$ を固定した桁 DP

上の桁から 1 桁ずつ置く。いまの値を $v$ とすると、次の桁 $d$ を置いた値は $10v+d$ なので、余りは $m\to(10m+d)\bmod s$、桁和は $r\to r+d$。$r>s$ になった状態は二度と $s$ に戻らない(桁和は減らない)ので捨てる。

3「$N$ 以下」の扱い

$N$ と先頭が一致している状態は常に 1 通り($N$ の先頭そのもの)。そこから $N$ の桁より小さい数字を置いた瞬間に「$N$ より確実に小さい」側へ移り、以後は 0〜9 を自由に置ける。先頭に 0 を置くと桁数の少ない数を表す(0 は桁和にも余りにも影響しない)。最後に先頭一致の状態=$N$ 自身を数え忘れないこと(入力例 2)。

4届かない桁和を捨てる枝刈り

$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(実測)。

5Python で速くする工夫

余りの遷移 $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 での計測

自己評価

自分の回答

気づき・メモ