题目链接
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式 输出一个整数,表示最大价值。
数据范围 0
输入样例 :
4 5
1 2
2 4
3 4
4 5
输出样例: 8
f[i][j]:只看前i个物品,总体积是j,总价值最大是多少
res = max{f[n][0…v]},f[i][j]这里是前i件物品的一个子集,最终结果要在所有可能的体积中选取最大的,(如果开始时数组初始化为0,那f[n][v]就是最终结果)
转移方程:
f[i][j] = max{1,2},
1.不选第i件物品,f[i][j] = f[i-1][j]
2.选第i个物品,f[i][j] = f[i - 1][j - v[i]] + w[i];
f[0][0] = 0;
时间复杂度:O(N^2)
空间复杂度:O(N^2)
#include
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];
int v[N], w[N];
int main(){
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 = 0; j <= m; ++j){
f[i][j] = f[i - 1][j];
if(j >= v[i]){
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
}
}
}
int res = 0;
for(int i = 0; i <= m; ++i)
res = max(res, f[n][i]);
cout << res << endl;
return 0;
}
空间优化
定义的动态规划数组,每一行的状态都只和上一行的状态有关,所以这里采用一维数组来保存上一行的状态,为了不覆盖上一行的状态这里采取从m到0逆序来计算。
说明:这里求解的f[m]就是最终的结果,即容量为m时,背包的最大价值。而不用在f[0…m]中取最大的。
特别说明 对于降维成一维的情况:
1.f[i] = 0(所有的都初始化为0), 最终结果是f[m]
2.f[0] = 0, f[i] = -INF i != 0(只有f[0]初始化为0),(其实这里求得是容量恰好为i时的最大价值,所以最终结果要取其中最大的)最终结果是max{f[0…m]}
初始化细节问题:
1.如果题目要求是恰好装满背包时的最大价值,初始化的时候需要将f[0]初始化为0,其余初始化为-INF,可以这样理解:当背包容量为0时,恰好装满背包只能用体积为0的物品来装,那其余的容量的背包均没有合法的解,属于未定义的状态将其初始化为-INF。最终结果是f[m]
2.如果题目要求是不超过背包的容量时的最大价值,初始化时将f[0…m]都初始化为0,可以这样理解:只要不超过背包的容量就可以,那每一个背包都有一个合法的解“什么都不装”,价值为0,也就是初始状态将背包的价值置为0。最终结果是f[m]
本题属于第2种情况
#include
using namespace std;
const int N = 1010;
int n, m;
int f[N];
int v[N], w[N];
int main(){
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] << endl;
return 0;
}
题目链接
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式 输出一个整数,表示最大价值。
数据范围 0
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例: 10
对比01背包问题,状态依然是物品和重量,只不过选择的情况多了,01背包只考虑k=0,1的情况,完全背包要考虑所有的k
#include
using namespace std;
const int N = 1010;
int f[N][N];
int v[N], w[N];
int n,m;
int main(){
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 = 0; 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[n][m];
return 0;
}
优化空间复杂度:
#include
using namespace std;
const int N = 1010;
int f[N];
int v[N], w[N];
int n,m;
int main(){
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){
for(int k = 1; k * v[i] <= j; ++k){
f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
}
}
}
cout << f[m];
return 0;
}
基于01背包优化空间复杂度的写法进行变形
01背包优化空间复杂度是将数组降维成一维,为了使当前的结果不覆盖之前的结果,采取逆序遍历的方式。
完全背包问题是每种物品的个数不加以限制,因此,这里可以不用考虑覆盖的情况。也就是说:01背包是基于前i-1个物品进行计算,而完全背包是考虑的前i个物品。
f[j - v[i]]
不包含第i件物品f[j - v[i]]
包含第i件物品#include
using namespace std;
const int N = 1010;
int f[N];
int v[N], w[N];
int n,m;
int main(){
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[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m];
return 0;
}
上述思想的二维数组形式,针对01背包原始写法的改进
f[i - 1][j - v[i]] + w[i]
考虑的是不选择第i件物品的最大价值加上当前物品的价值,保证该物品只选择1件f[i][j - v[i]] + w[i]
考虑的是选择第i件物品的最大价值加上当前物品的价值,该物品可多次选择注:将该写法降维可得上述写法
#include
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];
int v[N], w[N];
int main(){
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 = 0; j <= m; ++j){
f[i][j] = f[i - 1][j];
if(j >= v[i]){
f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);
}
}
}
int res = 0;
for(int i = 0; i <= m; ++i)
res = max(res, f[n][i]);
cout << res << endl;
return 0;
}
题目链接
有 N 种物品和一个容量是 V 的背包。 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值。
输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式 输出一个整数,表示最大价值。
数据范围 0
输入样例
4 5
1 2
3 2
4 1
3 4
3 4
5 2
输出样例:
10
多重背包问题是01背包问题的扩展,01背包对于当前物品只有两种选择,多重背包给定了当前物品的最多选择的件数
#include
using namespace std;
const int N = 110;
int n, m;
int f[N];
int main(){
cin >> n >> m;
for(int i = 0; i < n; ++i){
int v, w, s;
cin >> v >> w >> s;
for(int j = m; j >= 0; --j){//依然要逆序遍历
for(int k = 1; k <= s && k * v <= j; ++k){
f[j] = max(f[j], f[j - k * v] + k * w);
}
}
}
cout << f[m];
return 0;
}
多重背包问题的优化:
基本思想:将多重背包问题转化成01背包问题
每个物品都有s份,可以将同一件物品的s份分成不同的s件物品,意思就是说:01背包问题是每个物品都有一份,那我们将这s件物品都放到数组内,就变成了01背包问题。
如果我们按照将这s份物品,都拆成一件一件的,转化成01背包问题,就类似于:我们把7拆成7个1,然后按照01背包的问题来求解。这样转化正确的原因在于:由于将当前物品放入背包中可能放入的是k件,k<=7,任何一个小于k的数都能够用所拆分成的7个1来表示。那有没有更高效的办法呢?
现在的问题是:将7至少拆分成几个数,使得这几个数都能组合成任意小于等于7的数?
乘法原理拆分: 每个数可以选或者不选,至少用几个数可以将这个数s表示出来, log(s)
7 = 1 + 2 + 4,任意小于等于7的数都能够用1,2,4这三个数来组成。10 = 1 + 2 + 4 + 3,任意小于等于10的数都能够用1,2,3,4这4个数来组成,这四个数取或者不取,
0 = 全都不取
1 = 1
2 = 2
3 = 3
4 = 4
5 = 1 + 4
6 = 2 + 4
……
总结:用乘法原理进行拆分,将拆分好的数目组成新的物品,物品的价值为w[i] * k,体积为v[i] * k,将拆分好的物品放入物品列表中,按照01背包的思想进行计算
#include
using namespace std;
const int N = 2010;
int n, m;
int f[N];
struct Good{
int v, w;
};
int main(){
cin >> n >> m;
vector<Good> goods;
for(int i = 0; i < n; ++i){
int v, w, s;
cin >> v >> w >> s;
for(int k = 1; k <= s; k *= 2){
s -= k;
goods.push_back({k * v, k * w});
}
if(s)
goods.push_back({s * v, s * w});
}
for(auto g : goods){
for(int j = m; j >= g.v; --j){
f[j] = max(f[i], f[i - g.v] + g.w);
}
}
cout << f[m];
return 0;
}
题目链接
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值。输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次; si=0 表示第 i 种物品可以用无限次; si>0 表示第 i 种物品可以使用 si 次;
输出格式 输出一个整数,表示最大价值。数据范围 0
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例: 8
混合背包问题是01背包、完全背包、多重背包问题的综合,可以将这三类看成两类,也就是将多重背包转化成01背包,这样可分别对01背包和完全背包这两类问题进行求解
#include
using namespace std;
struct Good{
int kind;
int v, w;
};
const int N = 1010;
int f[N];
int n ,m;
int main(){
cin >> n >> m;
vector<Good> goods;
for(int i = 0; i < n; ++i){
int v, w, s;
cin >> v >> w >> s;
if(s == -1)
goods.push_back({-1, v, w});
else if(s == 0)
goods.push_back({0, v, w});
else{
for(int k = 1; k <= s; ++k){
s -= k;
goods.push_back({-1, v * k, w * k});
}
if(s)
goods.push_back({-1, v * s, w * s});
}
}
for(auto g : goods){
if(g.kind == -1){
for(int i = m; i >= g.v; --i)
f[i] = max(f[i], f[i - g.v] + g.w);
}
else{
for(int i = g.v; i <= m; ++i){
f[i] = max(f[i], f[i - g.v] + g.w);
}
}
}
cout << f[m];
return 0;
}
题目链接
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。 每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。 求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。 输出最大价值。
输入格式 第一行两个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。
输出格式 输出一个整数,表示最大价值。
数据范围 0
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例: 8
二维费用的背包问题是01背包问题的扩展,01背包只要求不超过背包的最大容量,二维费用的背包要求不超过背包的最大容量以及背包的重量。
具体实现方法:
#include
using namespace std;
const int N = 110;
int f[N][N];
int n, v, m;
int main(){
cin >> n >> v >> m;
for(int i = 0; i < n; ++i){
int a, b, c;
cin >> a >> b >> c;
for(int j = v; j >= a; --j){
for(int k = m; k >= b; --k){
f[j][k] = max(f[j][k], f[j - a][k - b] + c);
}
}
}
cout << f[v][m];
return 0;
}
题目链接
有 N 组物品和一个容量是 V 的背包。 每组物品有若干个,同一组内的物品最多只能选一个。 每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。 求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。 输出最大价值。
输入格式 第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量; 每组数据接下来有 Si 行,每行有两个整数
vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值; 输出格式 输出一个整数,表示最大价值。数据范围 0
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
分组背包问题其实和多重背包问题类似,多重背包是分组背包的一个特殊情况,特殊在将一个物品的s件看成一个组,组内的物品有每个有1…s件,在这其中选一种。分组背包中组内的物品是不同的,依然是组内物品选择一个。
对比01背包问题:
#include
using namespace std;
const int N = 110;
int f[N], v[N], w[N];
int n, m;
int main(){
cin >> n >> m;
for(int i = 0; i < n; ++i){
int s;
cin >> s;
for(int j = 0; j < s; ++j){
cin >> v[j] >> w[j];
}
for(int j = m; j >= 0; --j){
for(int k = 0; k < s; ++k){
if(j - v[k] >= 0)
f[j] = max(f[j], f[j - v[k]] + w[k]);
}
}
}
cout << f[m];
return 0;
}
题目链接
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出 最优选法的方案数。注意答案可能很大,请输出答案模> 10^9+7 的结果。
输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式 输出一个整数,表示 方案数 模 109+7 的结果。
数据范围 0
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例: 2
该问题是求01背包问题的最优方案数
具体做法:
#include
using namespace std;
const int N = 1010, INF = 1000000;
int n, m;
int f[N],g[N];//f统计最大价值,g统计最大价值的方案数
int main(){
cin >> n >> m;
int res = 0;
for(int i = 1; i <= m; ++i)//统计的是体积恰为j时的最大价值
f[i] = -INF;
g[0] = 1;
for(int i = 0; i < n; ++i){
int v, w;
cin >> v >> w;
for(int j = m; j >= v; --j){
int t = max(f[j], f[j - v] + w);
int s = 0;
//需要知道该方案是从哪个状态转移过来的,以便统计该方案数量
if(t == f[j])
s += g[j];
if(t == f[j - v] + w)
s += g[j - v];
s %= 1000000007;
f[j] = t;
g[j] = s;
}
}
int max_w = 0;
for(int i = 0; i <= m; ++i){
max_w = max(max_w, f[i]);
}
for(int i = 0; i <= m; ++i){
if(f[i] == max_w)
res += g[i];
res %= 1000000007;
}
cout << res;
return 0;
}
题目链接
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。 这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式 输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。
物品编号范围是 1…N。
数据范围 0
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
1 4
该题目是求01背包问题的具体的方案,可能会有多种方案,这里要求输出字典序最小的。
具体做法:
#include
using namespace std;
const int N = 1010;
int n, m;
int f[N];
bool cmp(vector<int> &num1, vector<int> &num2){
int i;
for(i = 0; i < num1.size() && i < num2.size(); ++i){
if(num1[i] < num2[i])
return true;
else if(num1[i] > num2[i])
return false;
}
if(i < num1.size())
return false;
else
return true;
}
int main(){
cin >> n >> m;
vector<vector<int>> res(m + 1);
for(int i = 0; i < n; ++i){
int v, w;
cin >> v >> w;
for(int j = m; j >= v; --j){
int t = max(f[j], f[j - v] + w);
if(f[j] == f[j - v] + w){//当前物品不装入和装入结果一样,保存最小的
vector<int> temp = res[j - v];
temp.push_back(i);
if(!cmp(res[j], temp))
res[j] = temp;
}
else if(t == f[j - v] + w){
res[j] = res[j - v];
res[j].push_back(i);
f[j] = t;
}
//cout << t << endl;
}
}
for(auto a : res[m]){
cout << a + 1 << " ";
}
return 0;
}
说明:还有一种做法是,先求最优解再反推方案,那这就必须用二维数组,以便获得具体的方案。
由于这里要求按字典序输出,那么对于物品可以采取从后往前的方式来遍历,如果当前物品放入最终结果会使得方案最优,那就一定放入。
反推方案数的时候从第一个物品开始判断
#include
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];
int v[N], w[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> v[i] >> w[i];
for(int i = n; i > 0; --i){//从后往前求最优解
for(int j = 0; j <= m; ++j){
f[i][j] = f[i + 1][j];
if(j >= v[i])
f[i][j] = max(f[i + 1][j], f[i + 1][j - v[i]] + w[i]);
}
}
//int val = m;
for(int i = 1; i <= n; ++i){//从前往后推方案
if(m >= v[i] && f[i][m] == f[i + 1][m - v[i]] + w[i]){
cout << i << " ";
m -= v[i];
}
}
return 0;
}