予鈴

アウトプットとメモ書きの中間みたいな記事がたくさん出ます。

典型ナップサックを1次元配列で解く。

※一番PV数が多いので、ちょっと直しました(2020/5/24)

例題として、 B - 書き換え(Rewrite)を使います。
まずはこのコードを見てください。 

#include<bits/stdc++.h>
using namespace std;
int main(){
    int N,M;
    cin >> N >> M;
    vector<int>V(N),T(N);
    for(int i = 0; i < N; ++i){
        cin >> V[i] >> T[i];
    }
    vector<int>dp(M + 1,0);
    for(int i = 0; i < N; i++)
        for(int t = M; t >= 0;t--){
            if(t + T[i] <= M)
                dp[t + T[i]] = max(dp[t + T[i]], dp[t] + V[i]);
        }
    cout << *max_element(dp.begin(), dp.end()) << endl;
    
}

シンプルですね。時間計算量は O(NM)ですが、空間計算量は O(M)で少しお得です。

まずは2次元で考える。

二次元DPで考えると、
 dp[i][j] := i個目の書類を見ている時に,j時間使ったときの最大値
 dp[i + 1][j + T[i]]=max (dp[i + 1][j + T[i]],dp[i][j]+V[i]) という漸化式になります。


さて、この問題を1次元配列で解くことを考えます。
dp[i] :=時間iのときの最大の価値
つまり、どの書類を見ているか、という情報を捨てます。

以下が工夫するポイントです。

後ろ向きに更新する

時間 tを後ろからなめて、次のように更新します。

for(int t = M; t >= 0; --t)

 dp[t + T[i]] = max(dp[t + T[i]],dp[t] + v[i])
嬉しいポイントは、値の更新がすでに見た tに対して行われます。つまり、 i番目の書類を重複して更新されることがありません。

荷物を取らない推移ないけど?

必要無いからです。2次元DPの際は, i番目の書類まで見た結果を i + 1番目にも反映させる必要があるますが、この場合はありません。