背包问题的背景
将n个物品放入某个体积为V的背包中,根据所给的条件限制
求取背包中物品的最大价值和
在基础阶段总体分为
01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
朴素版算法
时间复杂度:O(N*M);
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int v[N],w[N];
int f[N][N];
int main(){
int n,m;
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(v[i]<=j)f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
cout<<f[n][m];
return 0;
}
优化版本
二维变一维(减少空间复杂度的耗损,减少MLE的可能)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int w[N],v[N];
int g[N];
int n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
for(int i=1;i<=n;i++){
难点2(滚动数组的思想的实现)
for(int j=m;j>=w[i];j--){
//细节实现的过程中要注意
难点1(//g[i][j]=g[i-1][j];)
g[j]=max(g[j],g[j-w[i]]+v[i]);
}
}
cout<<g[m];
return 0;
}
难点1 :g[i][j]=g[i-1][j] ==>g[j]=g[j]
==>直接注释掉(由DP过程中的集合的定义来解释,g[i]的几何意义是背包中存放物品的体积不超过i的情况下所取得的最大价值,故在没有新的物品加入的情况下 g[j]应该保持不变)
重点在于个人意愿在此处我想得到的是被覆盖后的值或者被覆盖前的值(在此处这两个值应该是相等的),在不放第i个物品的情况下是等价关系g[i-1][j]==g[i][j]
难点2 : 改变数组遍历的顺序
原因分析:
原式 if(v[i]<=j)f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
根据j的大小从小到大m进行分析:
f[i][j]的数据意义为只选择 i 个物品,得到的可以存放物品价值的最大值,因此在上式中f [ i ] [ j - v [ i ] ]的数据意义为选择前i个物品得到的最大价值(即被覆盖后的数值),而我们想要得到的是选择前i-1个物品得到的最大价值(即被覆盖前的值.在正向遍历的过程中,max(f[i][j],f[i-1][j-v[i]]+w[i]) 式子中先覆盖了f[j-v[i]]的值,因此在f[j]包括了f[j-v[i]]的情况,不能像难点1一样直接果断地去掉第一维的坐标而是选择从后进行遍历在f[j]先覆盖的值,f[j-v[i]]为后来覆盖的值符合在二维时情况下的题意
ps:在此可以理解为在二维的情况下,一维的坐标限定了覆盖前后的关系 ,而在一维的情况下就要尤其记得分析覆盖的前后顺序
完全背包问题
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
朴素版
时间复杂度分析:O(nmw[i]/k)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int w[N],v[N];
int g[N][N];
int n,v_n;
int main(){
cin>>n>>v_n;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=v_n;j++){
g[i][j]=g[i-1][j];
//细节实现的过程中要注意
for(int k=0;k*w[i]<=j;k++){
if(j>=k*w[i])g[i][j]=max(g[i][j],g[i-1][j-k*w[i]]+k*v[i]);
}
}
}
cout<<g[n][v_n];
return 0;
}
优化版本I:
时间复杂度:O(n*m)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int v[N],w[N];
int f[N][N];
int main(){
int n,m;
cin>>n>>m;
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++){
f[i][j]=f[i-1][j];
if(j>=v[i])f[i][j]=max(f[i][j],f[i][j-w[i]]+v[i]);
}
}
cout<<f[n][m];
return 0;
}
数学意义的证明如下:
if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i],f[i-1][j-2w[i]]+2v[i],f[i-1][j-3w[i]]+3v[i]........);
f[i][j-w[i]]=max(f[i][j-w[i]],f[i-1][j-2w[i]]+v[i],f[i-1][j-3w[i]]+2v[i],f[i-1][j-4w[i]]+3v[i]........);
两式结合得f[i][j]=max(f[i][j],f[i][j-w[i]]+v[i])
优化最终版(进一步优化空间复杂度):
时间复杂度O(n*m)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int w[N],v[N];
int g[N];
int n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
for(int i=1;i<=n;i++){
for(int j=w[i];j<=m;j++){
g[j]=max(g[j],g[j-w[i]]+v[i]);
}
}
cout<<g[m];
return 0;
}
多重背包问题
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
朴素版
时间复杂度:O(nm2000)
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],k[N];
int f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i]>>k[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=f[i-1][j];
for(int q=1;q<=k[i] && j>=q*v[i];q++){
f[i][j]=max(f[i][j],f[i-1][j-q*v[i]]+q*w[i]);
}
}
}
cout<<f[n][m];
return 0;
}
二进制优化版
时间复杂度:O(nmlog(2000))
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
const int M=2010;
int g[N][M];
int w[N],v[N];
int s[N];
int n,m,idx;
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
int a,b,c;
cin>>a>>b>>c;
int k=1;
while(k<=c){
idx++;
w[idx]=k*a;
v[idx]=k*b;
c-=k;
k*=2;
}
if(c>0){
idx++;
w[idx]=c*a;
v[idx]=c*b;
}
}
n=idx;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
g[i][j]=g[i-1][j];
if(j>=w[i])g[i][j]=max(g[i][j],g[i-1][j-w[i]]+v[i]);
}
}
cout<<g[n][m];
return 0;
}
分组背包问题
有 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
朴素版
时间复杂度:O(n* m*w[i]/k)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int w[N][N],v[N][N];
int f[N][N];
int s[N];
int n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
for(int j=0;j<s[i];j++){
cin>>w[i][j]>>v[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
for(int k=0;k<s[i];k++){
if(j>=w[i][k])f[i][j]=max(f[i][j],f[i-1][j-w[i][k]]+v[i][k]);
}
}
}
cout<<f[n][m];
return 0;
}