01背包:题目
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,V
,用空格隔开,分别表示物品数量和背包容积。接下来有 N
行,每行两个整数 vi,wi
,用空格隔开,分别表示第 i
件物品的体积和价值。输出格式
输出一个整数,表示最大价值。数据范围
0<N,V≤10000<vi,wi≤1000
1.理解题意:
给定n个物品的重量以及价值,每个物品只有一个 取或不取,求指定容量能够得到价值的最大值。
2.给出状态定义
dp[i][j] 只取前i个物品 并且当前背包容量为 j的时候能够获取价值的最大值。
3.状态转移方程
dp[i][j] = max(dp[i-1][j],dp[i-1][j-v]+w) // 当前第i个物品可以取或者不取,那么当前获得价值的最大值应该为二者之间的最大值,所以当前最大价值可以由这两个状态获取
4.初始状态
dp[0][0]=0 // 当一个物品不取且背包容量为0时获取最大价值为0
5.编写代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e3 + 10;
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];
f[0][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;
}
6.代码优化
发现dp[i][j] 状态都是由 dp[i-1][] 状态转移过来的所以省略第一维 逆序遍历背包容量
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N= 1e3 + 10;
int f[N],n,m;
int main(){
cin>>n>>m;
for(int i = 1; i <= n; i++){
int v,w;
cin>>v>>w;
for(int j = m; j >= v; j --){
f[j] = max(f[j],f[j-v]+w);
}
}
cout<<f[m]<<endl;
return 0;
}
完全背包问题
有 N 种物品和一个容量是W的背包,每种物品都有无限件可用。
第 i𝑖 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,V𝑁,𝑉,用空格隔开,分别表示物品种数和背包容积。
接下来有 N𝑁 行,每行两个整数 vi,wi𝑣𝑖,𝑤𝑖,用空格隔开,分别表示第 i𝑖 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<𝑁,𝑉≤1000
0<vi,wi≤1000
1.理解题意
和01背包不同的是每种商品可以获取的数量不做限制
2.给出状态定义
dp[i][j] 只取前i个物品 并且当前背包容量为 j的时候能够获取价值的最大值。
3.状态转移方程
可以发现我们最终想要的价值转移方程为:
f[i][j] = max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,...)
注意到:
f[i][j-v] = max(f[i-1][j-v],f[i-1][j-2v]+w,...)
所以我们最终的转移方程可以变为:
f[i][j] = max(f[i-1][j],f[i][j-v]+w)
优化方案:
由于f[i][j] 是由f[i-1][j]和f[i][j-v]+w得到所以第一维可以优化并且状态方程 正序遍历背包容量即可
优化代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int N= 1e3 + 10;
int f[N];
//f[i][j] = max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,...)
//f[i][j-v] = max(f[i-1][j-v],f[i-1][j-2v]+w,...)
//f[i][j] = max(f[i-1][j],f[i][j-v]+w)
int n,m;
int main(){
cin>>n>>m;
for(int i = 1; i <= n; i++){
int v,w;
cin>>v>>w;
for(int j = v; j <= m; j++){
f[j]= max(f[j],f[j-v]+w);
}
}
cout<<f[m];
return 0;
}
3.多重背包
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000<𝑁,𝑉≤100
0<vi,wi,si≤100
1.理解题意
和01背包 、完全背包不同的是限制每种物品的数量,可以取0个1个.. si个
2.给出状态定义
dp[i][j] 只取前i个物品 并且当前背包容量为 j的时候能够获取价值的最大值。
3.状态转移方程
dp[i][j] = max(dp[i-1][j-v]+w,dp[i-1][j-2*v]+2*w,.......,dp[i-1][j-si*v]+si*w)
和01背包优化方案同理,dp[i][j] 只与dp[i-1][]有关,省略第一维,从大到小枚举背包容量即可
4.编写代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110;
int f[N];
int n,m;
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 >= v; 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]<<endl;
return 0;
}
5.二进制优化
每个物品的数量是一定的取多少数量的物品可以由二进制来表示 比如背包容量为 17 17=1+2+4+8+2,可以将每个元素都当做一个物品他的物品体积和价值可以按照对应的个数以及单价来计算,将每个物品重复操作,可以得到一个列表里面是物品的价值以及对应的体积,将多重背包问题转化成了01背包问题,套用01背包进行求解,降低复杂度。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int,int> PII;
const int N = 2e3 + 10;
int f[N];
int main(){
vector<PII> goods;
int n,m;
cin>>n>>m;
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>0){
goods.push_back({s*v,s*w});
}
}
for(int i = 0; i < goods.size(); i ++){
for(int j = m; j>=goods[i].first; j-- ){
f[j] = max(f[j],f[j-goods[i].first]+goods[i].second);
}
}
cout<<f[m]<<endl;
return 0;
}
4.分组背包问题
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i是组号,j 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
- 每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
- 每组数据接下来有 Si 行,每行有两个整数 vij,wij用空格隔开,分别表示第 i 个物品组的第 j个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000<𝑁,𝑉≤100
0<Si≤1000<𝑆𝑖≤100
0<vij,wij≤100
1.题目理解
这次是很多组,每组只能选一个物品,
2.状态定义
dp[i][j] 只取前i组物品 背包容量是j 能获得价值最大值。
3.状态转移
dp[i][j] =max(dp[i-1][j-vi]+wi,...) 对于组内的每个物品是选或不选,对所有组做相同操作。
4.代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e2 + 10;
int f[N],v[N],w[N];
int main(){
int n,m;
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(v[k] <= j) f[j] = max(f[j],f[j-v[k]]+w[k]);
}
}
}
cout<<f[m]<<endl;
return 0;
}