(1)01背包
有N件物品和一个容量为V的背包。第i件物品的价格(即体积,下同)是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
解题思想:对第i件物品选不选的问题。
状态方程:f[i][j] = f[i - 1][j] //第i件没有选
f[i][j] = max(f[i - 1][j - w[i]] + c[i], f[i - 1][j]) //第i件被选
最开始不理解这两个方程是什么意思,为什么要用这两个方程来表示,后来渐渐明白是用二维数组来表示所有的情况,j - w[i]表示当j满足j > w[i]时,前i - 1 的价值加上 i 的价值。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1010;
int f[maxn][maxn];
int main()
{
int t, n, c[maxn], v[maxn];
cin >> t >> n;
for(int i = 1; i <= n; i++)
cin >> c[i] >> v[i];
memset(f, 0, sizeof(f));
for(int i = 1; i <= n; i++){
for(int j = 0; j <= t; j++)
f[i][j] = f[i - 1][j]; //第i件没有选的价值
for(int j = 0; j <= t - c[i]; j++) //对所有减去第i件时间的穷举
f[i][j] = max(f[i - 1][j], f[i - 1][j - c[i]] + v[i]); //第i件被选的价值
}
int sum = 0;
for(int i = 1; i <= n; i++)
sum = max(sum, f[n][i]); //找到最大值
cout << sum << endl;
return 0;
}
简化一下也可以用一维数组来求解,从背包最大的容量开始一点一点的存储,最后的那一个值就是最大值
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1010;
int f[maxn];
int main()
{
int m, n, v[maxn], c[maxn];
cin >> m >> n;
for(int i = 1; i <=n; i++)
cin >> v[i] >> c[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]] + c[i]);
}
cout << f[m] << endl;
return 0;
}
(2)完全背包
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这由01背包的两种选择变为了选择,不选择和选择多少个三个选择。
将完全背包转化为01背包,进行拆分,把重复可用的想成相同的价值不同的物品进行叠加。
状态转移方程:f[i][v]=max(f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v)
差分成01背包优化: f[j] = max(f[j],f[j-c[i]]+w[i])
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1010;
int n, m, c[maxn], v[maxn];
int main()
{
int f[maxn];
cin >> m >> n;
for(int i = 1; i <= n; i++)
cin >> c[i] >> v[i];
memset(f, 0, sizeof(f)); //注意清零,不清零会任意赋值出现错误
for(int i = 1; i <= n; i++){
for(int j = c[i]; j <= m; j++)
f[j] = max(f[j - c[i]] + v[i], f[j]);
}
cout << f[m] << endl;
return 0;
}
(3)多重背包
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
状态转移方程:f[i][v]=max(f[i-1][v-k*w[i]]+ k*c[i]|0<=k<=n[i])
一道例题↓_↓
庆功会
【问题描述】
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
【输入格式】
第一行二个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。 接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和购买的数量(买0件到s件均可),其中v<=100,w<=1000,s<=10。
【输出格式】
第一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
【输入样例】
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
【输出样例】
1040
未优化算法,在多重背包基础上再嵌套一个对次数的嵌套循环
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 6010;
int n, m, c[maxn], v[maxn], x[maxn];
int main()
{
int f[maxn];
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> c[i] >> v[i] >> x[i];
memset(f, 0, sizeof(f));
for(int i = 1; i <= n; i++){
for(int j = m; j >= 0; j--){
for(int k = 0; k <= x[i]; k++){ //对k的次数进行选择
if(j - k * c[i] < 0) break;
f[j] = max(f[j - k * c[i]] + k * v[i], f[j]);
}
}
}
cout << f[m] << endl;
return 0;
}