ゆらのふなびと

競プロ, Python, C++

yukicoder No.385 カップ麺生活

問題

M円の所持金がある。

N種類のカップ麺がある。i番目のカップ麺はCi円である。カップ麺は好きなものを好きなだけ購入できる。

カップ麺を購入したとき、もし残り所持金が素数となれば所持金をM円に戻すことができる(これを"金欠チャンス"と呼ぶ)。ただし同じ素数で金欠チャンスを使えるのは1回までである。

このとき、購入できるカップ麺の最大数を求めよ。

制約

1 <= M <= 10000

1 <= N <= 20

1 <= Ci <= 1000

解法

カップ麺は好きなものを好きなだけ購入できるので、到達できる素数はすべて金欠チャンスで使いたい。しかも、毎回の金欠チャンスまでにできるだけ多くのカップ麺を買いたい。

そこで、dp[i] = 所持金をiにするまでに購入できる最大のカップ麺の数 というDPを考える。これは個数制限なしのナップザック問題である(重み→C[i], 価値→1(どの商品も「1個」という価値を持つ))。このDPをした後、素数のiについてdp[i]を足し上げる。最後は素数にならずともカップ麺をできるだけ買えばよいので、dp配列の最大値をとる(あるいは最小の値段の商品をひたすら買う)。

#include <bits/stdc++.h>
using namespace std;
#define int long long   // <-----!!!!!!!!!!!!!!!!!!!

#define rep(i,n) for (int i=0;i<(n);i++)
#define rep2(i,a,b) for (int i=(a);i<(b);i++)
#define rrep(i,n) for (int i=(n)-1;i>=0;i--)
#define rrep2(i,a,b) for (int i=(b)-1;i>=(a);i--)
#define all(a) (a).begin(),(a).end()

typedef long long ll;
typedef pair<int, int> Pii;
typedef tuple<int, int, int> TUPLE;
typedef vector<int> V;
typedef vector<V> VV;
typedef vector<VV> VVV;
typedef vector<vector<int>> Graph;
const int inf = 1e9;
const int mod = 1e9 + 7;

// x > 0
bool isPrime(int n) {
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return false;
    }
    return n != 1;
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);

    int M, N;
    cin >> M >> N;
    V C(N);
    rep(i, N) cin >> C[i];

    V dp(M + 1, -inf);
    dp[M] = 0;
    rep(i, N) {
        for (int j = M - C[i]; j >= 0; j--) {
            dp[j] = max(dp[j], dp[j + C[i]] + 1);
        }
    }

    int ans = 0;
    rep2(i, 1, M) {
        if (dp[i] > 0 && isPrime(i)) {
            ans += dp[i];
        }
    }

    ans += *max_element(all(dp));
    cout << ans << endl;
}

部分和のDPがよくわかってなくて時間をとった。