01背包问题
问题描述
有 n 个物品和一个容量为 v 的背包,每个物品有两个属性,一个是其体积 vi ,一个是其价值(权重) wi ,每件物品最多只能用一次(每件物品只有一个,可用可不用),将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大
Dp 思想
左边的情况一定存在,但右边的情况不一定会存在,当 j<vi 的时候,右边的情况就不存在,故需要进行判断
实现
//f[0][0~m]=0,一件物品都没有选,所以其最大价值为0
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][j], f[i - 1][j - v[i]] + w[i]);
}
优化
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
——>f[j] = max(f[j], f[j - v[i]] + w[i]);
= f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);
j-v[i]<j,j 从小到大枚举,计算 f[j] 的时候,f[j-v[i]] 在第 i 层已经计算过了 f[j - v[i]]=f[i][j-v[i]]
//f(i) 计算时只用到了 f(i-1),f(0)~f(i-2) 是没有用的,可以用滚动数组完成
//j 和 j-wi 都是 ≤ j
for(int i=1;i<=n;i++)
for (int j = 0;j <= m;j++) {//--> j<v[i]是没有意义的——>j=v[i],判断也可以删除
//--> int j=m;j>=v[i];j--
f[i][j] = f[i - 1][j];//--> f[j]=f[j] 恒等式,可以删去
if (j >= v[i])//--> 删去
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
//-->f[j] = max(f[j], f[j - v[i]] + w[i]);
//== f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);
//j-v[i]<j,j 从小到大枚举,计算 f[j] 的时候,f[j-v[i]] 在第 i 层已经更新过
//存的是f[i][j-v[i]],而不是f[i-1][j-v[i]]
//故f[j - v[i]]=f[i][j-v[i]]
//若想要使得f[j-v[i]]=f[i-1][j-v[i]],需要 j 从大到小进行遍历
//此时,f[j-v[i]] 在第 i 层没有更新,仍然存的是 f[i-1][j-v[i]]
//f[j-v[i]]=f[i-1][j-v[i]]
}
//最终代码
for(int i=1;i<=m;i++)
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
实现
例题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次
第 i 件物品的体积是 vi,价值是 wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值
输出格式
输出一个整数,表示最大价值
数据范围
0<N,V≤1000
0<vi,wi≤1000输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8
解答
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
cin >> n >> m;
for (int i = 1;i <= n;i++)
cin >> v[i] >> w[i];
//f[0][0~m]=0,一件物品都没有选,所以其最大价值为0
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][j], f[i - 1][j - v[i]] + w[i]);
}
cout << f[n][m] << endl;
return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[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 的背包,每个物品有两个属性,一个是其体积 vi ,一个是其价值(权重) wi ,每件物品可用无限次(每件物品有无限个),将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大
Dp 思想
实现
for (int i = 1;i <= n;i++)
for (int j = 0;j <= m;j++)
for (int k = 0;k * v[i] <= j;k++) //k不可以无限大,k个物品的总体积要小于等于j
f[i][j] = max(f[i - 1][j], f[i - 1][j - k * v[i]] + w[i] * k);
优化
f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,f[i-1,j-3v]+3w,...)
f[i,j-v]=max( f[i-1,j-v] ,f[i-1,j-2v]+w ,f[i-1,j-3v]+2w,...)
粉色部分的最大值就是 f[i,j-v]+w
f[i,j]=max(f[i-1,j],f[i,j-v]+w)
——>只枚举两个状态就可以,不需要枚举 k 个状态
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][j], f[i][j - v[i]] + w[i]);
}
for (int i = 1;i <= n;i++)
for (int j = v[i];j <=m;j++) {//因为f[i][j]是从f[i][j-v[i]]优化得到的
//计算第i层数据的时候,并不需要第i-1层数据
//所以j直接从小到大遍历即可
f[j] = max(f[j], f[j - v[i]] + w[i]);
//f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
例题
有 N种物品和一个容量是 V 的背包,每种物品都有无限件可用
第 i 种物品的体积是 vi,价值是 wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大
输出最大价值
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积
接下来有 N 行,每行两个整数 vi, wi,用空格隔开,分别表示第 i 件物品的体积和价值
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
10
解答
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][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++)
for (int k = 0;k * v[i] <= j;k++) //k不可以无限大,k个物品的总体积要小于等于j
f[i][j] = max(f[i - 1][j], f[i - 1][j - k * v[i]] + w[i] * k);
cout << f[n][m] << endl;
return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][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][j], f[i][j - v[i]] + w[i]);
}
cout << f[n][m] << endl;
return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[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 = v[i];j <=m;j++) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
cout << f[m] << endl;
return 0;
}
多重背包
问题描述
有 n 个物品和一个容量为 v 的背包,每个物品有三个属性,一个是其体积 vi ,一个是其价值(权重) wi ,还有一个是物品的个数 si ,将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大
Dp 思想
f[i][j]=max(f[i-1][j-v[i]*k]+w[i]*k);k=0,1,2,...,s[i]
实现
for (int i = 1;i <= n;i++)
for (int j = 0;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]] + w[i] * k);
优化
完全背包的优化方式——不可行
f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,f[i-1,j-3v]+3w,...,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-1,j-(s+1)v]+sw)
给出 f[i,j-v]
的最大值,并不能求出来粉色部分的最大值
二进制优化方式
假设有 s 个第 i 种物品,是否需要逐个枚举第 i 种物品的个数 0~s?
是不需要逐个枚举第 i 种物品的个数
可以将对这些物品进行分组,分为 k 组,每组中第 i 种物品的个数依次为 1 , 2 , 4 , 8 , ... 2k ,利用这 k 组去凑 s
使用 1 可以凑出 0~1
使用 1、2 可以凑出 0~3
使用 1 、2、4 可以凑出 0~7
...
每一组最多只能选一次,将多重背包问题转换成 0 1 背包问题
s=1023
1 | 0~1 |
1、2 | 0~3 (0~1 +2—>2~3,加上之前的 0~1,变成 0~3) |
1、2、4 | 0~7 (0~3 +4—>4~7,加上之前的 0~3,变成 0~7) |
... | |
1、2、4、...、512 | 0~1023 |
s=200
1 | 0~1 |
1、2 | 0~3 (0~1 +2—>2~3,加上之前的 0~1,变成 0~3) |
1、2、4 | 0~7 (0~3 +4—>4~7,加上之前的 0~3,变成 0~7) |
... | |
1、2、4、...、64 | 0~217 |
1、2、4、...、64、128 | 0~255(只有200个第 i 种物品,凑不出 201~255) |
1、2、4、...、64、73 | 0~217 (0~217 +73—>73~200,加上之前的 0~217,变成 0~200) |
如何凑?
第 i 种物品,有 s 个
1、2、4、8、...、2k 、c
c < 2k+1
使用 1、2、4、8、...、2k ,可以凑出 0~2k+1 -1,+c 之后就可以凑出 c~s ,一共可以凑出
0~s
若 c>2k+1 ,则 0~2k+1 -1 与 c~s 之间有空隙,无法凑出 0~s
故,c=s-(2k+1 -1)
二进制具体优化方式
将第 i 种的 s 个物品分成 log s 组 s[i]—>log s[i]
一共有 n 种物品,每种 s[i] 个,变成了一共有 log s[1]+log s[2]+... +log s[n] 个物品,对这些物品做 0 1 背包问题
int cnt = 0;//表示所有新物品的编号
for (int i = 1;i <= n;i++) {
int a, b, s;
cin >> a >> b >> s;//当前物品的体积,价值,个数
int k = 1;//从1开始分
while (k <= s) {
cnt++;
v[cnt] = a * k;//新物品的体积
w[cnt] = b * k;//新物品的价值
s -= k;
k *= 2;
}
if (s > 0) {//剩余的那部分,即 c
cnt++;
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]);
例题
例题1——数据范围小
有 N 种物品和一个容量是 V 的背包
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大
输出最大价值输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量
输出格式
输出一个整数,表示最大价值
数据范围
0<N,V≤100
0<vi,wi,si≤100输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
解答
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N];
int f[N][N];
int main() {
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 = 0;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]] + w[i] * k);
cout << f[n][m] << endl;
return 0;
}
例题2——数据范围大
有 N种物品和一个容量是 V 的背用
i 种物品最多有 si 件,每件体积是 vi,价值是 wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大
输出最大价值
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积
接下来有 N 行,每行两个整数 vi, wi,si 用空格隔开,分别表示第 i 件物品的体积和价值和数量
输出格式
输出一个整数,表示最大价值
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
解答
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 15000, M = 2010;//物品个数,1000*log 2000=12000
int n, m;
int v[N], w[N];
int f[N];
int main() {
cin >> n >> m;
int cnt = 0;//表示所有新物品的编号
for (int i = 1;i <= n;i++) {
int a, b, s;
cin >> a >> b >> s;//当前物品的体积,价值,个数
int k = 1;//从1开始分
while (k <= s) {
cnt++;
v[cnt] = a * k;//新物品的体积
w[cnt] = b * k;//新物品的价值
s -= k;
k *= 2;
}
if (s > 0) {//剩余的那部分,即 c
cnt++;
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] << endl;
return 0;
}
分组背包
问题描述
有 n 组物品和一个容量为 v 的背包,每组物品有若干个,每组最多只能选一个物品,将物品放在背包中,不能超过背包容量,怎样放使得物品价值和最大
Dp 思想
实现
for (int i = 1;i <= n;i++)//从前往后枚举每一组物品
for (int j = m;j >= 0;j--)//从大到小枚举所有体积
for (int k = 0;k < s[i];k++)//枚举所有选择
if (v[i][k] <= j)
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
例题
有 N 组物品和一个容量是 V 的背包
每组物品有若干个,同一组内的物品最多只能选一个
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大
输出最大价值
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量
接下来有 N 组数据:
- 每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
- 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例
8
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];//s存每组物品的个数
int f[N];
int main() {
cin >> n >> m;
for (int i = 1;i <= n;i++) {
cin >> s[i];
for (int j = 0;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 = 0;k < s[i];k++)//枚举所有选择
if (v[i][k] <= j)
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
cout << f[m] << endl;
return 0;
}