問題
http://yukicoder.me/problems/no/385
所持金M円で、N種類のカップ麺から「好きなものを好きなだけ」購入する。
i番目のカップ麺はCi円である。
もし、残金が素数になれば、素数1つにつき1回、所持金をM円を戻すことができる。
カップ麺は最大で何個買える?
1 <= M <= 10^5
1 <= N <= 20
1 <= Ci <= 10^3
考察
1. ナップサック問題感がすごい -> dpかな?
2. 「好きなものを好きなだけ」が気になる
ここでカップ麺を沢山買うときの戦略を考えてみる
3. どういう風に買えばたくさん買える?
4. なるべく所持金をM円に戻せばたくさん買える
5. 残金が素数となる買い方は全て試した上で、あとは、買えるだけ買えばいい
まず、残金がi円となる時に最大でいくつ買えるかを計算しておこう
6. dp[i番目までのカップ麺で][残金がj円となるときの] = 買える最大個数
これをdpで計算する
dp[i][j] -> dp[i+1][j], dp[i+1][j-C[i]], dp[i+1][j-C[i]*2], ...
実装
http://yukicoder.me/submissions/101159
int M, N; int C[20]; int dp[21][10001]; //----------------------------------------------------------------- vector<bool> primes; void make_primes(int n) { primes.resize(n + 1, true); primes[0] = primes[1] = false; rep(i, 2, sqrt(n)) { if (primes[i]) { for (int j = 0; i * (j + 2) < n; j++) primes[i * (j + 2)] = false; } } } //----------------------------------------------------------------- int main() { make_primes(101010); scanf("%d %d", &M, &N); rep(i, 0, N) scanf("%d", &C[i]); rep(i, 0, N + 1) rep(j, 0, M + 1) dp[i][j] = -1; dp[0][M] = 0; rep(i, 0, N) rep(j, 0, M + 1) if (0 <= dp[i][j]) { rep(k, 0, j / C[i]+1) dp[i + 1][j - k * C[i]] = max(dp[i + 1][j - k * C[i]], dp[i][j] + k); } int ans = 0; int _max = 0; rep(j, 0, M + 1) if (0 <= dp[N][j]) { if (primes[j]) ans += dp[N][j]; _max = max(_max, dp[N][j]); } ans += _max; printf("%d\n", ans); }