はまやんはまやんはまやん

hamayanhamayan's blog

カップ麺生活 [yukicoder 385]

問題

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], ...

7. あとは残金が素数となる場合は全部買えるので足して、最後に残金が0~M-1円で最も多く買える場面を足して答え

実装

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);
}