背包问题:
每个物品只有一件, 选择与不选择
\(dp[i][j]\) :从前i个物品里选, 总体积不超过j的选法集合
属性: 最大值
选择第i个物品:\(dp[i - 1][j - v[i]] + w[i]\)
不选第i个物品:\(dp[i - 1][j]\)
二者取最大值。
滚动数组优化为一维:
\(dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i])\)
\(dp[j] = max(dp[j], dp[j - v[i]] + w[i])\)
\(j\)从大到小枚举
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int w[N];
int v[N];
int dp[N];
int main()
{
int n, W;
cin >> n >> W;
for (int i = 1; i <= n; i ++ ){
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i ++ )
for (int j = W; j >= v[i]; j -- )
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout << dp[W] << endl;
return 0;
}
每件物品数量无限.
\(dp[i][j]\):从前i种物品中选择, 总体积不超过j的选法集合
属性:最大值
对于第i种物品:
一件都不选:\(dp[i - 1][j]\)
选择一件: \(dp[i - 1][j - v[i]] + w[i]\)
选择两件: \(dp[i - 1][j - 2 \times v[i]] + 2 \times w[i]\)
...
选择k件:\(dp[i - 1][j - k \times v[i]] + k \times w[i]\)
取最大值
时间优化:
空间优化:
\(f[i - 1][j] , f[i][j - v] + w --> f[i][j]\)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10;
int w[N];
int v[N];
int dp[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 ++ )
dp[j] = max(dp[j - v[i]] + w[i], dp[j]);
cout << dp[m] << endl;
return 0;
}
每个物品都有多个, 数量有限
\(dp[i][j]\):从前i种物品中选, 总体不超过j的选法集合
属性:最大值
类似于完全背包:
一件都不选:\(dp[i - 1][j]\)
选择一件: \(dp[i - 1][j - v[i]] + w[i]\)
选择两件: \(dp[i - 1][j - 2 \times v[i]] + 2 \times w[i]\)
...
选择\(s[i]\)件:\(dp[i - 1][j - s[i] \times v[i]] + s[i] \times w[i]\)
取最大值
由于\(s[i]\)限制, 无法应用完全背包的方法进行优化
多重背包的优化方式:
一、二进制优化
对于任意一个十进制数字总可以分割成若干个二进制之和,对于每一个\(s[i]\), 进行二进制拆分,拆分后的若干个背包转化为选与不选的01背包
二、单调队列优化
无优化:
/*
f[i][j] : 选前i中物品, 总体积不超过j时, 所有可能性的一个集合
属性: 最大值
1. 不选第i种物品: f[i - 1][j]
2. 选第i种物品: f[i][j - v] + w, f[i][j - 2 * v] + w * 2 , ..., f[i][j - s * v] + w * s
*/
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int dp[N][N];
int s[N], w[N], v[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 ++ ){
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
for (int k = 1; k * v[i] <= j && k <= s[i]; k ++ )
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i] * k] + w[i] * k);
}
cout << dp[n][m] << endl;
return 0;
}
二进制优化:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = 2010;
int dp[N];
int w[N], v[N], cnt;
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
int vx, wx, s;
cin >> vx >> wx >> s;
int k = 1;
while (k <= s){
cnt ++;
v[cnt] = k * vx;
w[cnt] = k * wx;
s = s - k;
k <<= 1;
}
if (s > 0){
cnt ++;
v[cnt] = s * vx;
w[cnt] = s * wx;
}
}
//for (int i = 1; i <= cnt; i ++ )
// cout << w[i] << ‘ ‘ << v[i] << endl;
for (int i = 1; i <= cnt; i ++ )
for (int j = m; j >= v[i]; j -- )
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout << dp[m] << endl;
return 0;
}
有若干组物品, 每一组物品只能选择其中一种
\(dp[i][j]\):从前i类物品里选择, 总体积不超过j的选法集合
属性:最大值
对于第i类物品:
不选:\(dp[i - 1][j]\)
选择第一件: \(dp[i - 1][j - v[i][1]] + w[i][1]\)
选择第二件: \(dp[i - 1][j - v[i][2]] + w[i][2]\)
...
选择第k件: \(dp[i - 1][j - v[i][k]] + w[i][k]\)
取最大值
一维优化:
\(dp[i - 1][j - v[i][k]] + w[i][k] --> dp[j - v[i][k]] + w[i][k]\)
逆向枚举
#include <iostream>
using namespace std;
const int N = 110;
int w[N][N];
int v[N][N];
int dp[N];
int s[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
cin >> s[i];
for (int j = 1; j <= s[i]; j ++ )
cin >> v[i][j] >> w[i][j];
}
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 0; j -- )
for (int k = 1; k <= s[i]; k ++ )
if (j >= v[i][k])
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
cout << dp[m] << endl;
return 0;
}
原文:https://www.cnblogs.com/lhqwd/p/14630155.html