動的プログラミング
ナップサック問題
目次
01 バックパック問題
動的プログラミング
- ステータス表示
f[i][j]
- コレクション: 最初の i 項目を考慮し、ボリュームが j を超えないすべての選択方法
- 属性:マックス
- 状態コンピューティング: コレクションの分割
N個のアイテムと容量Vのバックパックがあります。各アイテムは 1 回のみ使用できます。
i 番目の項目の体積はvi v_iですv私は,价值是 w i w_i w私は。
リュックサックの容量を超えず、合計値が最大になるように、リュックサックにどのアイテムを入れるかを考えます。
最大値を出力します。入力フォーマット
最初の行では、2 つの整数 N と V がスペースで区切られており、それぞれアイテムの数とバックパックの体積を示しています。
次に N 行があり、それぞれに 2 つの整数vi 、 wi v_i、w_i が含まれます。v私は、w私はスペースで区切られた は、それぞれ i 番目のアイテムのボリュームと値を表します。
出力フォーマット
最大値を表す整数を出力します。
データ範囲
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000< vi , wi v_i,w_iv私は、w私は≤1000入力サンプル
4 5 1 2 2 4 3 4 4 5
出力例:
8
二次元
-
状態の定義 f[i][j]: 最初の i 項目、ナップザック容量 j の下での最適解 (最大値)
-
ナップザックの容量が十分な場合、i 番目のアイテムを選択するかどうかを決定する必要があります。
- 選ばないでください
f[i][j] = f[i-1][j]
- 選択する
f[i][j]=f[i-1][j-v[i]]+w[i]
- 最大値を取得する方法を決定するため、上記の 2 つのケースでは次の時間がかかります。
max()
コード
#include <iostream> #include <algorithm> using namespace std; const int N = 1010; int v[N], w[N]; int f[N][N]; int main() { int n, m; cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> v[i] >> w[i]; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { f[i][j] = f[i - 1][j]; if (v[i] <= j) f[i][j] = max(f[i][j], f[i -1][j - v[i]] + w[i]); } } cout << f[n][m]; return 0; }
- 選ばないでください
一次元
私たちが定義した状態はf[i][j]
i と j の任意の合法的な最適解を取得できますが、タイトルには最終状態のみが必要なf[n][m]
ので、状態を更新するために必要なのは 1 次元空間だけです。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; ++i) {
for (int j = m; j >= v[i]; --j) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m];
return 0;
}
完全なナップサック問題
動的プログラミング
- ステータス表示
f[i][j]
- コレクション: 最初の i 項目を考慮し、ボリュームが j を超えないすべての選択方法
- 属性:マックス
- 状態コンピューティング: コレクションの分割
f[i][j]
i 番目のアイテムとして k 個のアイテムを選択し、最初に k 個のアイテム i を削除し、次に k 個のアイテム i を返します。
f[i][j] = f[i-1][j-v[i]*k]+w[i]*k
暴力的な二穴同時挿入
#include <iostream>
using namespace std;
const int N = 1010;
int f[N][N],v[N], w[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
for (int k = 0; k * v[i] <= j; ++k) {
f[i][j] = max(f[i][j],f[i - 1][j - k * v[i]] + k * w[i]);
// cout << f[i][j];
}
}
}
cout << f[n][m];
return 0;
}
私たちは知ることができます
f[i,j]=Max(f[i-1,j],f[i-1,j-v]+w,f[i-1.j-2v]+2w,...,f[i-1.j-kv]+kw
f[i,j-v]=Max( f[i-1,j-v],f[i-1.j-2v]+w,...,f[i-1.j-kv]+(k-1)w
コード
#include <iostream>
// #include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N],v[N], w[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
f[i][j] = f[i-1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);
}
}
cout << f[n][m];
return 0;
}
1次元コード
#include <iostream>
// #include <algorithm>
using namespace std;
const int N = 1010;
int f[N],v[N], w[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; ++i) {
for (int j = v[i]; j <= m; ++j) {
// f[i][j] = f[i-1][j];
f[j] = max(f[j], f[j-v[i]] + w[i]);
}
}
cout << f[m];
return 0;
}
複数のナップザックの問題
動的プログラミング
- ステータス表示
f[i][j]
- コレクション: 最初の i 項目を考慮し、ボリュームが j を超えないすべての選択方法
- 属性:マックス
- 状態コンピューティング: コレクションの分割
トピックの説明
N個のアイテムと容量Vのバックパックがあります。
i 項目には最大でもsi s_i が含まれますs私は個、各個の体積はvi v_iv私は ,价值是 w i w_i w私は。
アイテムの体積の合計がバックパックの容量を超えず、値の合計が最大になるように、バックパックにどのアイテムを入れるかを解決します。
最大値を出力します。入力フォーマット
最初の行では、2 つの整数 N と V がスペースで区切られており、それぞれアイテムの数とバックパックの体積を表しています。
次に N 行があり、それぞれに 3 つの整数vi v_iが含まれます。v私は、ウィウィ_イw私は、し、しs私はスペースで区切られた は、それぞれ i 番目のアイテムの体積、値、数量を表します。
出力フォーマット
最大値を表す整数を出力します。
データ範囲
0<N,V≤100
0< vi v_iv私は、ウィウィ_イw私は、し、しs私は≤100入力サンプル
4 5 1 2 3 2 4 1 3 4 3 4 5 2
出力例:
10
コード
#include <iostream>
using namespace std;
const int N = 110;
int f[N][N], w[N], v[N], s[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
for (int k = 0; k * v[i] <= j && k <= s[i]; ++k) {
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
cout << f[n][m];
return 0;
}
データ範囲が広がる場合
データ範囲
0<N≤1000
0<V≦2000
0< vi v_iv私は、ウィウィ_イw私は、し、しs私は≤2000
f(i, j) = Max(f(i-1,j),f(i-1,j-v)+w,f(i-1,j-2v)+2w+...+f(i-1,j-sv)+sw)
f(i, j-v) = Max(f(i-1,j-v),f(i-1,j-2v)+w,f(i-1,j-3v)+2w+...+f(i-1,j-sv)+(s-1)w,f(i, j) = Max(f(i-1,j),f(i-1,j-v)+w,f(i-1,j-2v)+2w+...+f(i-1,j-(s+1)v)+sw)
したがって、完全なナップザック問題では解決できません
バイナリ最適化+01 ナップザック問題の手法を採用できます。
バイナリ最適化
リンゴの束と 10 個の箱がある場合、リンゴを n 個選択します。このリンゴの束を 1, 2, 4, 8, 16, ... 512 に従って 10 個の箱に分割すると、任意の数 x∈[0,1023] (11 番目の箱は 1024 を取得できるため、コメント領域があります)これについては議論) はこれら 10 個の箱の中のリンゴの数から表すことができますが、この方法での選択数は ≤ 10 倍になります
コード
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int v[N], w[N];
int f[2020];
int main() {
int n, m;
cin >> n >> m;
int cnt = 0;
while (n--) {
int a, b, s;
cin >> a >> b >> s;
int k = 1;
while (k <= s) {
v[++cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
if (s){
v[++cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt;
for (int i = 1; i <=n; ++i) {
for (int j = m; j >= v[i]; --j) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m];
return 0;
}
集団ナップサック問題
N セットのアイテムと容量 V のバックパックがあります。
各グループには複数の項目があり、同じグループ内で選択できる項目は最大 1 つだけです。
各項目の体積はvi , j v_{i,j}です。v私、 j、値はwi 、 j w_{i,j}です。w私、 jここで、i はグループ番号、j はグループ番号です。アイテムの合計がバックパックの容量を超えず、合計値が最大になるように、どのアイテムをバックパックに入れるかを解決します。
最大値を出力します。
入力フォーマット
最初の行には、スペースで区切られた 2 つの整数 N、V があり、それぞれアイテム グループの数とバックパックの容量を表します。
次に N セットのデータがあります。
- データの各セットの最初の行には整数S i S_{i}が含まれます。S私は、i 番目の項目グループ内の項目の数を示します。
- 各データセットの後にはS i S_{i}が続きますS私は行。各行には 2 つの整数vi 、 j v_{i,j}が含まれます。v私、 j、wi 、 j w_{i,j}w私、 jスペースで区切られた は、それぞれ i 番目の項目グループ内の j 番目の項目の量と値を表します。
出力フォーマット
最大値を表す整数を出力します。
データ範囲
0<N,V≤100
0<Si≤100
0< vi , j v_{i,j}v私、 j、wi 、 j w_{i,j}w私、 jj≤100入力サンプル
3 5 2 1 2 2 4 1 3 4 1 4 5
出力例:
8
コード
#include <iostream>
using namespace std;
const int N = 110;
int v[N], w[N];
int f[110];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
int s;
cin >> s;
for (int j = 1; j <= s; ++j) {
cin >> v[j] >> w[j];
}
for (int k = m; k >= 1; --k) {
for (int j = 1; j <= s; ++j) {
if (v[j] <= k)
f[k] = max(f[k], f[k - v[j]] + w[j]);
}
}
}
cout << f[m];
return 0;
}