問題はこちら
問題概要
解説
桁DPというものを用いると解くことができます.実装方法には様々な種類があるので代表的なものについて解説を行います.桁DPの概念については他の分かりやすい記事を参考にしてください.以下のけんちょんさんの記事がおすすめです。
以下,の上から
桁目の数を
,
の桁数を
と表します.
未満フラグを持つDP
(
桁目の数
として
を選んだ場合)
(
桁目の数
として
を選んだ場合)
からの遷移は以下のものがあります.
(
桁目の数
として
を選んだ場合)
これをそのまま書けば求めることができます.
Pythonプログラム
K = input()
D = int(input())
L = len(K)
DP = [[[0]*2 for _ in range(D)] for _ in range(L+1)]
MOD = 10**9+7
DP[0][0][0] = 1
for i in range(L):
for j in range(D):
for x in range(10):
if x == int(K[i]):
DP[i+1][(j+x)%D][0] += DP[i][j][0]
DP[i+1][(j+x)%D][0] %= MOD
if x < int(K[i]):
DP[i+1][(j+x)%D][1] += DP[i][j][0]
DP[i+1][(j+x)%D][1] %= MOD
DP[i+1][(j+x)%D][1] += DP[i][j][1]
DP[i+1][(j+x)%D][1] %= MOD
print((DP[L][0][0]+DP[L][0][1]-1)%MOD)
未満フラグを持たない方法
からは
への遷移があります.
また,桁目で初めて
より小さいことが確定するような数(
桁まで
と同じで,
桁目が
より小さい数)の分も考える必要があります.
は
を満たす
について桁和が
の倍数であるものの数となるので,これから
の分を引いて
の分を確認して足せば答えが求まります.
Pythonプログラム
K = input()
D = int(input())
L = len(K)
DP = [[0] * D for _ in range(L+1)]
MOD = 10**9+7
S = 0
for i in range(L):
for x in range(10):
for j in range(D):
DP[i+1][(j+x)%D] += DP[i][j]
DP[i+1][(j+x)%D] %= MOD
if 0 <= x < int(K[i]):
DP[i+1][(S+x)%D] += 1
DP[i+1][(S+x)%D] %= MOD
S += int(K[i])
if S%D == 0:
print(DP[L][0])
else:
print((DP[L][0]-1)%MOD)
今回の問題はLeadingZeroがあったとしても(0は和の単位元なので)問題なく数えられますが,LeadingZeroがあってはならない問題もあります.*1
この場合はDPを上位桁まで決めたとき以下の条件を満たすものの数と定義すればよいです.
より小さいことが確定している
- すべての桁が
ではない(
でない)
すると,以下のように分けられます.
から
への遷移
桁目で初めて
より小さいことが確定するような数(
桁まで
と同じ)
桁目で初めて
でない数が現れるような数.
最後にの分を確認して足せば答えが求まります.
Pythonプログラム
K = input()
D = int(input())
L = len(K)
DP = [[0] * D for _ in range(L+1)]
MOD = 10**9+7
for i in range(1,int(K[0])):
DP[1][i%D] += 1
DP[1][i%D] %= MOD
S = int(K[0])
for i in range(1,L):
for x in range(10):
for j in range(D):
DP[i+1][(j+x)%D] += DP[i][j]
DP[i+1][(j+x)%D] %= MOD
if 0 <= x < int(K[i]):
DP[i+1][(S+x)%D] += 1
DP[i+1][(S+x)%D] %= MOD
if 1 <= x:
DP[i+1][x%D] += 1
DP[i+1][x%D] %= MOD
S += int(K[i])
if S%D == 0:
print((DP[L][0]+1)%MOD)
else:
print(DP[L][0])
メモ化再帰
とします.の位の数字を
としたときの場合分けをします.
のとき
のとき
となるので再帰で解くことができます。
実はを計算するために再帰していく際,第
引数には「
の上位
桁」か「
の上位
桁
」の形の数しか現れません.
したがって,全体でしかかかりません.
C++プログラム
#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
constexpr int MOD = 1e9 + 7;
unordered_map<ll,ll> memo;
string K;
int D;
// 0以上K[:x](tight?以下:未満)の整数のうち,桁和をDで割った余りがdとなるものの数
ll solve(int x, int tight, int d) {
if (memo.count(x*D*2+tight*D+d)) return memo[x*D*2+tight*D+d];
if (x == -1 && tight == 1 && d == 0) return 1;
else if (x == -1) return 0;
ll res = 0;
int k = K[x]-'0'-1+tight;
for (int i = 0; i <= k; i++) {
res += solve(x-1,1,((d-i)%D+D)%D);
res %= MOD;
}
for (int i = k+1; i < 10; i++) {
res += solve(x-1,0,((d-i)%D+D)%D);
res %= MOD;
}
return memo[x*D*2+tight*D+d] = res;
}
int main() {
cin >> K >> D;
cout << (solve(K.size()-1,1,0)+MOD-1)%MOD << endl; // 0の分引く
return 0;
}
以下の記事も参考になります.
maspypy.com
場合分け
と上位
桁が一致する
桁の整数(
)
と
桁目が異なる
桁の整数
桁の整数(
)
そのもの
それぞれのグループに属する整数のうち,桁和で割った余りが
となるものの数を数えます.
このとき,以下の値が必要になるのであらかじめ基本的なDPにより求めておきましょう.
整数列
それぞれ以下のように求められます.
と上位
桁が一致する
桁の整数(
)
と
桁目が異なる
桁の整数
桁の整数(
)
そのもの
Pythonプログラム
K = input()
D = int(input())
L = len(K)
F = [[0] * D for _ in range(L+1)]
MOD = 10**9+7
ans = 0
# f(N,d)の計算
F[0][0] = 1
for i in range(L):
for j in range(D):
for x in range(10):
F[i+1][(j+x)%D] += F[i][j]
F[i+1][(j+x)%D] %= MOD
S = 0
for k in range(1,L):
S += int(K[k-1])
S %= D
for xk in range(int(K[k])):
SS = (S + xk) % D
ans += F[L-k-1][(D-SS)%D]
ans %= MOD
for x1 in range(1,int(K[0])):
ans += F[L-1][(D-x1)%D]
ans %= MOD
for k in range(1,L):
for xk in range(1,10):
ans += F[k-1][(D-xk%D)%D]
ans %= MOD
S += int(K[-1])
if S % D == 0:
ans += 1
print(ans)
オートマトンDP
桁DPはオートマトン上のDPと捉えることができる.C++プログラム
他の桁DPの問題は以下の記事にまとめています。
kanpurin.hatenablog.com