【分野別　初中級者が解くべき過去問精選 100 問】を初級者がC++で解く【動的計画法：ナップザック DP】

[レッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編：目指せ水色コーダー！】](https://qiita.com/e869120/items/eb50fdaece12be418faa)の`2-3. 分野別　初中級者が解くべき過去問精選 100 問`を初級者がC++で解いていきます。

# 動的計画法：ナップザック DP

## [34　ALDS_10_A - フィボナッチ数](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_10_A&lang=ja)
再帰だとTLEするので計算結果をメモ化する。vectorに順番に値を保持していくだけ。
```c++
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define repeq(i, a, b) for (int i = a; i <= b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int n;
  cin >> n;
  
  vector<int> dp(n + 1);
  dp.at(0) = 1;
  dp.at(1) = 1;
  
  rep(i, 2, n + 1) dp.at(i) = dp.at(i - 2) + dp.at(i - 1);
  cout << dp.at(n) << endl;
}
```
https://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=7404784#1

## [35　DPL_1_B - 0,1ナップザック問題](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DPL_1_B&lang=ja)
ナップサック問題の例題そのままなので簡単。
```c++
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define repeq(i, a, b) for (int i = a; i <= b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int N, W;
  cin >> N >> W;
  
  vector<int> value(N), weight(N);
  rep(i, 0, N) cin >> value.at(i) >> weight.at(i);
  
  vector<vector<int>> dp(N + 1, vector<int>(W + 1, -1));
  rep(i, 0, W + 1) dp.at(0).at(i) = 0;
      
  rep(i, 0, N) {
    rep(j, 0, W + 1) {
      if (j >= weight.at(i)) dp.at(i + 1).at(j) = max(dp.at(i).at(j - weight.at(i)) + value.at(i), dp.at(i).at(j));
      else dp.at(i + 1).at(j) = dp.at(i).at(j);
    }
  }
  cout << dp.at(N).at(W) << endl;
}
```
https://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=7405395#1

## [36　DPL_1_C - ナップザック問題](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DPL_1_C&lang=ja)
前問とほぼ同じだが、重複ありで選べるようになっている点が異なる。遷移方法が変わることにかなり戸惑ったが、紙に配列を書いて処理を一つずつ追っていったら理解できた。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define repeq(i, a, b) for (int i = a; i <= b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int N, W;
  cin >> N >> W;
  
  vector<int> value(N), weight(N);
  rep(i, 0, N) cin >> value.at(i) >> weight.at(i);
  
  vector<vector<int>> dp(N + 1, vector<int>(W + 1, -1));
  rep(i, 0, W + 1) dp.at(0).at(i) = 0;
      
  rep(i, 0, N) {
    rep(j, 0, W + 1) {
      if (j >= weight.at(i)) dp.at(i + 1).at(j) = max(dp.at(i + 1).at(j - weight.at(i)) + value.at(i), dp.at(i).at(j));
      else dp.at(i + 1).at(j) = dp.at(i).at(j);
    }
  }
  cout << dp.at(N).at(W) << endl;
}
```
https://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=7405871

## [37　DPL_1_A - コイン問題](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DPL_1_A&lang=ja)
重複ありのナップサックとほぼ同じ。今回は実装時の配列を1次元にしてみた。
頭がこんがらがったので普通に2次元でやればよかった。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int n, m;
  cin >> n >> m;
  
  vector<int> coins(m);
  rep(i, 0, m) cin >> coins.at(i);
  
  vector<int> dp(n + 1, 99999);
  dp.at(0) = 0;
  
  rep(i, 0, m) {
    rep(j, 1, n + 1) {
      if(j >= coins.at(i)) dp.at(j) = min(dp.at(j), dp.at(j - coins.at(i)) + 1);
    }
  }
  cout << dp.at(n) << endl;
}
```
https://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=7409446

## [38　ALDS_10_C - 最長共通部分列](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_10_C&lang=ja)
dp.at(i).at(j)でXのi文字目までとYのj文字目までの共通部分列の長さを表現する。dpの問題は、dpテーブルで何を表現するかさえ思いつけば、あとは簡単なのかもしれない、と思い始めた。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int q;
  cin >> q;
  
  rep(i, 0, q) {
    string X, Y;
    cin >> X >> Y;
    
    int xs = X.size();
    int ys = Y.size();
    
    vector<vector<int>> dp(xs + 1, vector<int>(ys + 1, 0));
    rep(i, 0, xs) {
      rep(j, 0, ys) {
        if (X.at(i) == Y.at(j)) dp.at(i + 1).at(j + 1) = dp.at(i).at(j) + 1;
        else dp.at(i + 1).at(j + 1) = max(dp.at(i + 1).at(j), dp.at(i).at(j + 1));
      }
    }
    cout << dp.at(xs).at(ys) << endl;
  }
}
```
https://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=7448684#1

## [39　JOI 2011 予選 4 - 1 年生](https://atcoder.jp/contests/joi2011yo/tasks/joi2011yo_d)
i番目の数を選んだ場合に答えがjになる組み合わせの数をdpテーブルで表現した。難しくはなかった。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int N;
  cin >> N;
  
  vector<int> A(N);
  rep(i, 0, N) cin >> A.at(i);
  
  vector<vector<ll>> dp(N - 1, vector<ll> (21, 0));
  dp.at(0).at(A.at(0)) = 1;
  rep(i, 0, N - 2) {
    rep(j, 0, 21) {
      if (dp.at(i).at(j) != 0) {
        if (j - A.at(i + 1) >= 0) dp.at(i + 1).at(j - A.at(i + 1)) += dp.at(i).at(j);
        if (j + A.at(i + 1) <= 20) dp.at(i + 1).at(j + A.at(i + 1)) += dp.at(i).at(j);
      }
    }
  }
  cout << dp.at(N - 2).at(A.at(N - 1)) << endl;
}
```
https://atcoder.jp/contests/joi2011yo/submissions/38752292

## [40　JOI 2012 予選 4 - パスタ](https://atcoder.jp/contests/joi2012yo/tasks/joi2012yo_d)
めちゃくちゃ難しくて過去イチで悩んだ。詰まって解答を調べても全然理解できなかった。初期状態を何で始めればいいのか分からなくて時間を取られた。結果的には「dp.at(0).at(0).at(0) = 1;」でよかった。dpテーブルの更新部分は「n-2日にiを選んでいる & n-1日にjを選んでいる & n日にkを選ぶ」を表現している。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

#define MOD 10000

int main() {
  int N, K;
  cin >> N >> K;
  
  vector<int> A(10010, 0);
  rep(i, 0, K) {
    int a, b;
    cin >> a >> b;
    A.at(a - 1) = b;
  }
  
  vector<vector<vector<int>>> dp(N + 1, vector<vector<int>>(4, vector<int>(4, 0)));
  dp.at(0).at(0).at(0) = 1;
  rep(n, 0, N) {
    rep(i, 0, 4) {
      rep(j, 0, 4) {
        rep(k, 1, 4) {
          if (A.at(n) != 0 && A.at(n) != k) continue;
          if (i != j || j != k) {
            dp.at(n + 1).at(k).at(j) += dp.at(n).at(j).at(i);
            dp.at(n + 1).at(k).at(j) %= MOD;
          }
        }
      }
    }
  }
  
  int ans = 0;
  rep(i, 1, 4) {
    rep(j, 1, 4) {
      ans += dp.at(N).at(i).at(j);
      ans %= MOD;
    }
  }
  
  cout << ans << endl;
}
```
https://atcoder.jp/contests/joi2012yo/submissions/38881014

## [41　JOI 2013 予選 4 - 暑い日々](https://atcoder.jp/contests/joi2013yo/tasks/joi2013yo_d)
i日目に服jを着た場合の「派手さの差の絶対値の合計」の最大値をdpテーブルで表す。i日目に服jを、i+1日目に服kを着られるなら値を更新する。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int D, N;
  cin >> D >> N;
  
  vector<int> T(D);
  rep(i, 0, D) cin >> T.at(i);
  
  vector<int> A(N), B(N), C(N);
  rep(i, 0, N) cin >> A.at(i) >> B.at(i) >> C.at(i);
  
  vector<vector<int>> dp(D, vector<int>(N, 0));
  rep(i, 0, D - 1) {
    rep(j, 0, N) {
      if ((T.at(i) < A.at(j)) || (B.at(j) < T.at(i))) continue;
      rep(k, 0, N) {
        if ((T.at(i + 1) < A.at(k)) || (B.at(k) < T.at(i + 1))) continue;
        dp.at(i + 1).at(k) = max(dp.at(i).at(j) + abs(C.at(j) - C.at(k)), dp.at(i + 1).at(k));
      }
    }
  }
  
  int mx = 0;
  rep(i, 0, N) mx = max(mx, dp.at(D - 1).at(i));
  cout << mx << endl;
}
```
https://atcoder.jp/contests/joi2013yo/submissions/38896743

## [42　JOI 2015 予選 4 - シルクロード](https://atcoder.jp/contests/joi2015yo/tasks/joi2015yo_d)
dpテーブルはi日目に都市jにたどり着いている場合の疲労度の最小値を表す。解法はすぐに思いついてコードも書けてテストケースも合っているのに、WAになって悩み続けてしまった。intだと途中でオーバーフローしていたのが原因だった。計算途中で値の制約を考慮に入れていないとこうなる。

dpの書き方を、配る遷移形式から貰う遷移形式に変えてみた。dpの遷移をイメージするのが苦手だったけど、貰う遷移形式にしたら急に書きやすくなった。初期状態で場合分けしてるのなんかダサいけど、分かりやすいからまあいいや。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (ll i = a; i < b; i++)
#define rrep(i, b, a) for (ll i = b; i >= a; i--)
#define repb(i, n) for (ll i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  ll N, M;
  cin >> N >> M;
  
  vector<ll> C(N), D(M);
  rep(i, 0, N) cin >> C.at(i);
  rep(i, 0, M) cin >> D.at(i);
  
  vector<vector<ll>> dp(M, vector<ll> (N, inf));
  rep(i, 0, M) {
    rep(j, 0, N) {
      if (((M - i) < (N - j)) || ((i) < (j))) continue;
      
      if (j == 0) {
        if (i == 0) dp.at(i).at(j) = D.at(i) * C.at(j);
        else dp.at(i).at(j) = min(D.at(i) * C.at(j), dp.at(i - 1).at(j));
      }
      else {
        dp.at(i).at(j) = min(dp.at(i - 1).at(j - 1) + D.at(i) * C.at(j), dp.at(i - 1).at(j));
      }
    }
  }
  
  cout << dp.at(M - 1).at(N - 1) << endl;
}
```
https://atcoder.jp/contests/joi2015yo/submissions/38904988

## [43　パ研杯2019 D - パ研軍旗](https://atcoder.jp/contests/pakencamp-2019-day3/tasks/pakencamp_2019_day3_d)
全然思いつかなかった。dpテーブルを入力と同じ5*Nで考えてしまった。公式解説を見たら、dpテーブルの行を「どの色を選ぶか」にしていて、なるほどなーと感動した。それさえ分かれば実装は一瞬だった(なんか無駄が多い書き方だなとは思うけど)。
```c++
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define repeq(i, a, b) for (int i = a; i <= b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  int N;
  cin >> N;
  
  vector<vector<char>> vec(5, vector<char> (N));
  rep(i, 0, 5) {
    rep(j, 0, N) {
      cin >> vec.at(i).at(j);
    }
  }
  
  vector<vector<int>> dp(3, vector<int> (N));
  rep(j, 0, N) {
    rep(i, 0, 3) {
      char color;
      if (i == 0) color = 'R';
      else if (i == 1) color = 'B';
      else if (i == 2) color = 'W';
      
      int cnt = 0;
      rep(k, 0, 5) if (vec.at(k).at(j) != color) cnt++;
      
      if (j == 0) dp.at(i).at(j) = cnt;
      else {
        int tmp = 999999;
        rep(k, 0, 3) if (i != k) tmp = min(dp.at(k).at(j - 1), tmp);
        dp.at(i).at(j) = tmp + cnt;
      }
    }
  }
  
  int ans = 99999;
  rep(i, 0, 3) ans = min(dp.at(i).at(N - 1), ans);
  cout << ans << endl;
}
```
https://atcoder.jp/contests/pakencamp-2019-day3/submissions/38916967

## [44　AOJ 1167 - ポロック予想](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1167&lang=jp)
重複ありナップサック問題。最初に提出したときは
`「N*Nのdpテーブルを作って、普通バージョンと奇数バージョンで2回dpする」`
を各クエリごとにやってしまって、無事エラーになった。最初に全部計算してしまって各クエリに対しては回答するだけ、という風にすればいいというのはいい学びになった。そしてやはりdpテーブルは一次元でよかった。コイン問題で苦しんだせいで避けてしまった。


```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define repeq(i, a, b) for (int i = a; i <= b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  vector<ll> dp(1000000, inf);
  vector<ll> dp2(1000000, inf);
  dp.at(0) = 0;
  dp2.at(0) = 0;
  
  rep(i, 0, 1000000) {
    rep(j, 1, 1000000) {
      int tetra = j * (j + 1) * (j + 2) / 6;
      if (i < tetra) break;
      
      dp.at(i) = min(dp.at(i - tetra) + 1, dp.at(i));
      if (tetra % 2 == 1) dp2.at(i) = min(dp2.at(i - tetra) + 1, dp2.at(i));
    }
  }
  
  while (1) {
    int N;
    cin >> N;
    if (N == 0) break;
    
    cout << dp.at(N) << " " << dp2.at(N) << endl;
  }
}
```
https://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=7482575#1

## [45　AOJ 2199 - 差分パルス符号変調](http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2199&lang=jp)
例によって、dpテーブルをどう置くかが難しく、実装は簡単だった。i番目の入力とコードブックのk番目を選んだときの出力がjで、二乗和の最小値がdpテーブルに保持される。
```c++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL << 60;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define repeq(i, a, b) for (int i = a; i <= b; i++)
#define rrep(i, b, a) for (int i = b; i >= a; i--)
#define repb(i, n) for (int i = 0; i < (1 << n); i++)
#define fore(i, n) for (auto &i : n)
#define all(x) x.begin(), x.end()
template<class T>bool chmax(T &a, const T &b) {if (a < b) {a = b; return 1;} return 0;}
template<class T>bool chmin(T &a, const T &b) {if (a > b) {a = b; return 1;} return 0;}

int main() {
  while (1) {
    int N, M;
    cin >> N >> M;
    if ((N == 0) && (M == 0)) break;
    
    vector<int> C(M), x(N);
    rep(i, 0, M) cin >> C.at(i);
    rep(i, 0, N) cin >> x.at(i);
    
    vector<vector<ll>> dp(N + 1, vector<ll>(256, inf));
    dp.at(0).at(128) = 0;
    rep(i, 0, N) {
      rep(j, 0, 256) {
        if (dp.at(i).at(j) == inf) continue;
        rep(k, 0, M) {
          ll nj = j + C.at(k);
          if (nj < 0) nj = 0;
          else if (nj > 255) nj = 255;
          dp.at(i + 1).at(nj) = min(dp.at(i).at(j) + (x.at(i) - nj) * (x.at(i) - nj), dp.at(i + 1).at(nj));
        }
      }
    }
    
    ll ans = inf;
    rep(i, 0, 256) ans = min(dp.at(N).at(i), ans);
    cout << ans << endl;
  }  
}
```
https://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=7497230#1