Learning Algorithms

アルゴリズムの勉強メモ

Atcoder Typical DP Contest E. 数

Atcoder Typical DP Contest E. 数

E: 数 - Typical DP Contest | AtCoder

解法

自分の実際の思考の流れに従ってまとめておく。

まず自明に思いつくDPが、$\ dp[i][j] := \ $(上からi番目までを見て、各桁の和をDで割った余りがjであるような組み合わせの数)である。しかしこれだけでは上手く漸化式を立てることができない。

まず、$\ N = 10^k\ $のときを考えよう。このとき一番左の桁を0にすれば、各桁の数は0から9までの数を自由に取りうる(すべて0になる場合は除く)。よって、上に述べたdpの更新が容易になる。例えば、$\ i + 1\ $番目の数が7だとするとDPの定義より、dp[i + 1][j] += dp[i][[(j - 7) % D]のように更新できる。これを$\ k = 0, 1,2, ..., 9\ $ですべて計算する。

次に、もう少し一般に、$\ N = a * 10^k\ $のときを考える。このとき、まず、1番目の数を0に固定すると、上に述べたようにその総数が求められる。次に1番目の数を1から$\ a - 1\ $に固定したときを考える。このときも実はほとんど同じように考えることができる。例えばこれを3に固定したとすれば、上のDPにおいて、$\ j = -3 = d - 3\ $となっているものをとってこればよいからである。これで1番目を0から$\ a - 1\ $に固定したときの答えは求めらた。

さて、これ以降、一番左の数は、$\ a\ $に固定して考えて良い。次の桁からも同じようにその桁の数を固定して考えていくのだが、2番目の数を固定したときに使えるdp値は、dp[n - 2][j]である。これを今までと同じように利用して計算する。ただし、固定した数をどこかに保存して、常にその数を引いたmodをとらなければいけない。 

あとから気づいたが、同じ桁の値でも、それが上限値をとっているのか的な変数をもたせてやれば、もっとわかりやすく書けそう。とか思いながら他の人の解説などを見てみると、まさにそうだった。というかそれを桁DPとかなんとか言うらしい。前も桁DPの問題をそうとは知らずに解いたような...。まぁいいや。

実装
#include "bits/stdc++.h"
using namespace std;

static const long long MOD = 1000000007;

signed main() {
        string s;
        int d;
        cin >> d >> s;
        int n = s.size();
        vector<vector<long long> > dp(n, vector<long long> (d, 0));
        dp[0][0] = 1;
        for (int i = 1; i < n; i ++) {
                for (int j = 0; j < d; j ++) {
                        for (int k = 0; k <= 9; k ++) {
                                dp[i][j] = dp[i][j] + dp[i - 1][(((j - k) % d) + d) % d] % MOD;
                        }
                }
        }
        long long ans = 0;
        long long ex = 0;
        for (int i = 0; i < n; i ++) {
                if (s[i] != '0') ans = ans + dp[n - 1 - i][((( - ex) % d) + d) % d] % MOD;
                for (int k = 1; k <= s[i] - '0' - 1; k ++) {
                        ans = ans + dp[n - 1 - i][(((- k - ex) % d) + d) % d] % MDO;
                }
                ex = (ex + s[i] - '0') % d;
        }
        cout << (ans - 1) % MOD << endl;
        return 0;
}