問題はこちら
問題概要
以上以下の整数のうち,十進表記における各桁の数字の総和がの倍数であるようなものはいくつか.
解説
桁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