01背包
特点:n件物品,每件物品只有一个,要么选要么不选
二维朴素版本
状态:前i件物品在总价值不超过j的前提下构成的最大价值
状态转移方程:if(j>w[i])dp[i][j]=dp[i-1][j]--放不下,不放
else dp[i][j]=max(dp[i-1][j]--放,dp[i-1][j-w[i]]+v[i]--不放)--可以放
cpp
#include<iostream>
using namespace std;
const int N = 3e1 + 10, M = 2e2 + 10;
int m, n, w[N], v[N],dp[N][M];
int main()
{
int n, m; cin >> m >> n;
for (int i = 1; i <= n; i++)
cin >> w[i] >> v[i];
//时间复杂度O(n^2)
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (j < w[i])dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}
cout << dp[n][m] << endl;
return 0;
}
01背包的滚动数组优化
cpp
//01背包的滚动数组优化
for (int i = 1; i <= n; i++)
{
//01背包滚动数组优化的时候,注意j要逆推
for (int j = m; j>=w[i]; j--)
{
dp[j]= max(dp[j], dp[j - w[i]] + v[i]);
}
}
完全背包
特点:n种物品,每件物品有无限件(但其实是有限 m/w[i]件)
朴素版本
cpp
for (int i = 1; i <= n; i++)
for (int j = m; j >= 1; j--)
for (int k = 1; k <= m / w[i]; k++)
if(j>=k*w[i])
dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
正推叠加优化
dp[j-w[i]]+v[i]=max{
dp[j-1*w[i]]+1*v[i],
dp[j-2*w[i]]+2*v[i],
dp[j-3*w[i]]+3*v[i],
dp[j-4*w[i]]+4*v[i],
...
dp[j-m/w[i]*w[i]]+m/w[i]*v[i]
}
cpp
//完全背包正推叠加优化
for (int i = 1; i <= n; i++)
//注意:完全背包j循环是正推(因为是一个一个拿)
for (int j = w[i]; j <= m; j++)
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
混合背包
多种背包组合,把01背包和完全背包统一处理成多重背包
cpp
#include<iostream>
using namespace std;
const int N = 40, M = 210;
int m, n, w[N], v[N], s[N], dp[M];
int main() {
//混合背包问题统计处理成多重背包
cin >> m >> n;
for (int i = 1; i <= n; i++) {
cin >> w[i] >> v[i] >> s[i];
if (s[i] == 0) {//完全背包
s[i] = m / w[i];
}
if (s[i] != 0 && s[i] != 1) {//多重背包
s[i] = min(s[i], m / w[i]);
}
}
//滚动数组优化
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 1;j--) {//逆推
for (int k = 1; k <= s[i]; k++) {
if (j >= k * w[i]) dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
}
}
}
cout << dp[m] << endl;
return 0;
}
多重背包
特 点:n种物品,每件物品有指定数量s[i](真实数量上限:min(m/w[i],s[i]))
01和完全都是多重背包的一种特殊情况
cpp
//多重背包朴素版本
for (int i = 1; i <= n; i++)
for (int j = m; j >= 1; j--)
//针对第i种物品,得到选k件的最优解
for (int k = 1; k <= min(m / w[i], s[i]); k++)
if (j >= k * w[i]) dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
二进制分解多重背包
cpp
#include<iostream>
using namespace std;
const int N = 5e3+10, M = 6e3+10;
int m, n, w[N], v[N],s[N], dp[M];
int main() {
cin >> n >> m; int ww, vv, ss;
int id = 0;
for (int i = 1; i <= n; i++) {
cin >> ww >> vv >> ss;//输入每种物品的容量、价值、数量
//针对ss进行二进制分解
for (int j = 1; j <= ss; j <<= 1) {
w[++id] = j * ww;
v[id] = j * vv;
ss -= j;
}
//如果二进制分解后的ss有剩余,则按剩余ss倍的物品存储
if (ss) {
w[++id] = ss * ww;
v[id] = ss * vv;
ss = 0;
}
}
//二进制优化多重背包时间复杂度O(nm) 其中n是二进制分解后的物品种类
//注意,二进制分解后的物品种类不再是n,是id
for (int i = 1; i <= id; i++) {
for (int j = m; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[m] << endl;
return 0;
}
二维费用背包
**特点:**二维费用背包(基础就是01背包)
状态:dp[j][k] 前i种物品在背包1容量不超过j的情况且背包2容量不超过k的情况下构成的最小价值
状态转移方程: dp[j][k]=min(dp[j][k],dp[j-w1[i]][k-w2[i]]+v[i]);
【注】需要处理负下标
cpp
#include<iostream>
using namespace std;
const int N = 1e3 + 10, M = 1e2 + 10;
int n, m1, m2;
int w1[N], w2[N], v[N], dp[M][M];
int main() {
cin >> m1 >> m2 >> n;
for (int i = 1; i <= n; i++)
cin >> w1[i] >> w2[i] >> v[i];
//注意,本题求最小价值,dp一定初始化为最大
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = m1; j >= 0; j--)
for (int k = m2; k >= 0; k--)
dp[j][k] = min(dp[j][k], dp[max(0,j - w1[i])][max(0,k - w2[i])] + v[i]);
cout << dp[m1][m2] << endl;
return 0;
}
分组背包
特点:n种物品,每种物品只有一件,分到不同的组中,每组最多选一件
状态和状态转移方程同01背包一样
cpp
#include<iostream>
#include<vector>
using namespace std;
/*
分组背包
特点:n种物品,每种物品只有一件,分到不同的组中,每组最多选一件
状态和状态转移方程同01背包一样
*/
const int N = 40, M = 210;
int m, n,t,p, w[N], v[N], dp[M];
int main() {
cin >> m >> n >> t;
vector<int> group[N];
for (int i = 1; i <= n; i++) {
cin >> w[i] >> v[i] >> p;
group[p].push_back(i);//根据组号p完成分组
}
//01背包可以看成特殊的分组背包,即每种物品都是单独的一组
for (int i = 1; i <= t; i++) {
for (int j = m; j >= 1; j--) {
//针对当前第i组的背包容量j,去枚举每件物品选/不选,找到其中的最优解dp[j](保证组内最多选一件)
for (int k = 0; k < group[i].size(); k++) {
int id = group[i][k];
if (j >= w[id]) dp[j] = max(dp[j], dp[j - w[id]] + v[id]);
}
}
}
cout << dp[m] << endl;
return 0;
}
有依赖背包
01背包就是特殊的有依赖背包
所有物品都是主件,并且每种物品只有一件,要么选,要么不选
有依赖背包
特点:有主件,还有附件,每种物品只有一件
不选主件
选主件(选0件附件)
选主件-选第一件附件
选主件-选第二件附件
选主件-选第一个和第二个附件
在以上五种情况中取最大值
cpp
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int m, n, main_w[N], main_v[N], sec_w[N][3], sec_v[N][3], dp[N], cnt[N];
int main() {
cin >> m >> n;
for (int i = 1; i <= n; i++) {
int v, p, q; cin >> v >> p >> q;
if (q == 0) {//主件处理
main_w[i] = v;
main_v[i] = v*p;
}
else {//附件处理 此时q对应的是主件的编号
cnt[q]++;//主件q的第几个附件
sec_w[q][cnt[q]] = v;//sec_w[q][cnt[q]]统计第q个主件的第cnt[q]件附件的容量
sec_v[q][cnt[q]] = v*p;//sec_cv[q][cnt[q]]统计第q个主件的第cnt[q]件附件的价值
}
}
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
//选主件,不选附件
if (j >= main_w[i]) dp[j] = max(dp[j], dp[j - main_w[i]] + main_v[i]);
//选主件-选第一件附件
if (j >= (main_w[i] + sec_w[i][1])) dp[j] = max(dp[j], dp[j - (main_w[i] + sec_w[i][1])] + main_v[i] + sec_v[i][1]);
//选主件-选第二件附件
if (j >= (main_w[i] + sec_w[i][2])) dp[j] = max(dp[j], dp[j - (main_w[i] + sec_w[i][2])] + main_v[i] + sec_v[i][2]);
//选主件-选第一个和第二个附件
if(j >= (main_w[i] + sec_w[i][1]+ sec_w[i][2])) dp[j] = max(dp[j], dp[j - (main_w[i] + sec_w[i][1]+sec_w[i][2])] + main_v[i] + sec_v[i][1]+sec_v[i][2]);
}
}
cout << dp[m] << endl;
return 0;
}
数字组合
cpp
#include<iostream>
using namespace std;
//01背包求方案数
//状态 dp[j] 前i个数字组合出数字j的方案数
//状态转移方程 dp[j]+=dp[j-w[i]]
const int N = 1e5 + 10;
int m, n, w[N], dp[N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = m; j >= w[i]; j--) {
dp[j] += dp[j - w[i]];
}
}
cout << dp[m] << endl;
return 0;
}
背包问题-背包求具体方案
cpp
#include<iostream>
using namespace std;
const int N = 40, M = 2e2 + 10;
int m, n, w[N], v[N], dp[N][M];//注意求具体方案时,需要二维dp
//背包求具体方案
int main() {
cin >> m >> n;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (j < w[i]) dp[i][j] = dp[i - 1][j];//放不下,不放
// 不选0 选1
else dp[i][j] = max(dp[i - 1][j], dp[i-1][j - w[i]] + v[i]);
}
}
//cout << dp[n][m] << endl;
//打印具体方案
int j = m;
for (int i = n; i >= 1; i--) {
if (dp[i][j] == dp[i - 1][j - w[i]] + v[i]) {
cout << i << " ";
j -= w[i];
}
}
return 0;
}