Yukicoder No.41 貯金箱の溜息(EASY)
座標圧縮ぽい。しかし、確かに座標は圧縮されているけど、最大値が小さい同じ問題に単に帰着しただけで、座標圧縮とは呼びたくない気がする。
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;
}