座標圧縮ぽい。しかし、確かに座標は圧縮されているけど、最大値が小さい同じ問題に単に帰着しただけで、座標圧縮とは呼びたくない気がする。

No.41 貯金箱の溜息(EASY)

解法

前処理で全部求める。$O(\frac{M}{C} + Q)$で、今回は$C = 111111$。

$1$円玉以外の硬貨の最大公約数が$111111$であるので、$M$円払うとき$M \bmod 111111$円分は全て$1$円玉で払わなければならない。 端数は当然全て$1$円玉であり、それ以外に$1$円玉を$1$枚でも使用すれば、残りの$111110$円分も全て$1$円玉で支払うことが確定する。

このため、$1$円玉硬貨A、$1$円玉硬貨B、$2 \dots 9$円玉硬貨の$10$種類の硬貨を使って$M’ = \lfloor \frac{M}{111111} \rfloor$円払う問題に帰着する。$M’$の最大値は小さいので、全場合に関して計算することができる。

$M’$に関しての計算であるが、硬貨を順番に処理していく。 $i$種類目までの硬貨を使って$j$円払う方法の数$dp_{i,j}$を更新する。

実装

  • $10^9+7$でなく$10^9+9$であることに注意。
  • $111111 \dots 666666$でなく$111111 \dots 999999$であることに注意。
#include <iostream>
#include <vector>
#include <cassert>
#define repeat(i,n) for (int i = 0; (i) < (n); ++(i))
typedef long long ll;
using namespace std;
const int coins[] = { 1, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const ll unit = 111111;
const ll limit = 1e10;
const ll len = (limit + unit - 1) / unit + 1;
const ll mod = 1e9+9;
int main() {
    // prepare
    vector<int> dp(len);
    dp[0] = 1;
    for (int k : coins) {
        repeat (init,k) {
            ll acc = 0;
            for (ll i = init; i < len; i += k) {
                ll t = dp[i];
                dp[i] = (dp[i] + acc) % mod;
                acc = (acc + t) % mod;
            }
        }
    }
    // input / output
    int t; cin >> t;
    repeat (i,t) {
        ll m; cin >> m;
        assert (m <= limit);
        cout << dp[m / unit] << endl;
    }
    return 0;
}