背包问题
0-1背包
例题:信奥一本通 1267
二维数组解法
时间复杂度O(n²),空间复杂度O(n²)
状态转移方程怎么来的已经非常熟悉了,如果j<w[i]即当前背包容量不够放下物品i,那么就是上一个状态的最大值;如果j>=w[i]就是说放得下了,有两种决策:拿或者不拿,如果不拿就是上一种状态,同上,如果拿了,背包就要空w[i]的空间出来,价值再加上c[i],注:容易MLE。
#include<bits/stdc++.h>
using namespace std;
const int maxn=30+1;
const int maxm=200+2;
int dp[maxn][maxm];
int w[maxn]; // 重量
int c[maxn]; // 价值
int main() {
int m,n;
cin>>m>>n; // m:背包容量,n:物品数
for(int i=1;i<=n;i++) {
cin>>w[i]>>c[i];
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
if(j<w[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);
}
}
// 输出dp表看看变化
for(int i=0;i<=n;i++) {
for(int j=0;j<=m;j++) {
cout<<dp[i][j]<<' ';
}
cout<<endl;
}
cout<<dp[n][m];
return 0;
}
一维数组解法
时间复杂度O(n²),空间复杂度O(n)
在动态规划中有个原则:后无效性原则,即当前状态只与上一个状态有关,而一维数组刚好可以满足这个特性,只需要找到dp[i]和dp[i-1]之间的状态转移方程即可。
当背包容量和最大物品数多起来过后,使用二维数组必定MLE(爆内存),所以需要压缩到一维数组。
一维数组只需要注意一个地方:为什么对于容量j是逆推?如果正向顺序更新,那么当前循环中计算dp[j]时,dp[j-w[i]]可能已经被更新,这样就会造成错误的结果。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5;
const int maxm=11;
int w[maxn]; // 重量
int c[maxn]; // 价值
int dp[maxm]; // 滚动数组
int main() {
int m,n;
cin>>m>>n; // m:背包容量,n:物品数
for(int i=1;i<=n;i++) {
cin>>w[i]>>c[i];
}
for(int i=1;i<=n;i++) {
for(int j=m;j>=1;j--) {
// 如果够,判断拿不拿
if(j>=w[i])
dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
}
// 每次赋值完后打印一遍这一行的数据
for(int j=1;j<=m;j++) {
cout<<dp[j]<<' ';
}
cout<<endl;
}
cout<<dp[m];
return 0;
}
完全背包
例题 信奥一本通 P1268
朴素法
时间复杂度 O(n³),空间复杂度O(n)
再设置一个遍历因子k,在一维01背包的基础上选择拿k个当前物品。
#include<bits/stdc++.h>
using namespace std;
const int maxn=30+3;
const int maxm=200+2;
int m,n; // 背包容量和物品数
int dp[maxm];
int w[maxn]; // 存储每个物品的重量
int c[maxn]; // 存储每个物品的价值
int main() {
cin>>m>>n;
for(int i=1;i<=n;i++) {
cin>>w[i]>>c[i];
}
for(int i=1;i<=n;i++) {
for(int j=m;j>=1;j--) {
// k最少拿0间,最多拿j/w[i]件
for(int k=0;k<=j/w[i];k++) {
if(j>=w[i]) {
dp[j]=max(dp[j],dp[j-k*w[i]]+k*c[i]);
}
}
}
}
cout<<"max="<<dp[m]<<endl;
return 0;
}
二维数组
时间复杂度O(n²),空间复杂度O(n²)
可以理解为dp[i] [j]的最优解来自与上一行的最优解或者当前行以前的最优解+新增的一个物品价值
#include<bits/stdc++.h>
using namespace std;
const int maxn=30+3;
const int maxm=200+2;
int m,n; // 背包容量和物品数
int dp[maxn][maxm];
int w[maxn]; // 存储每个物品的重量
int c[maxn]; // 存储每个物品的价值
int main() {
cin>>m>>n;
for(int i=1;i<=n;i++) {
cin>>w[i]>>c[i];
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
if(j>=w[i])
// 要看当前行的当前最优解
dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+c[i]);
else
dp[i][j]=dp[i-1][j];
}
}
cout<<"max="<<dp[n][m]<<endl;
return 0;
}
一维数组
时间复杂度O(n²),空间复杂度O(n)
因为dp[i] [j]的最优解可能来自于当前行最优解+一个物品的价值,所以需要顺推。
#include<bits/stdc++.h>
using namespace std;
const int maxn=30+3;
const int maxm=200+2;
int m,n; // 背包容量和物品数
int dp[maxm];
int w[maxn]; // 存储每个物品的重量
int c[maxn]; // 存储每个物品的价值
int main() {
cin>>m>>n;
for(int i=1;i<=n;i++) {
cin>>w[i]>>c[i];
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
if(j>=w[i])
dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
}
}
cout<<"max="<<dp[m]<<endl;
return 0;
}
多重背包
朴素法
时间复杂度O(n³),空间复杂度O(n)
#include<bits/stdc++.h>
using namespace std;
const int maxn=500+5;
const int maxm=6000+6;
int v[maxn]; // 价格
int w[maxn]; // 价值
int s[maxn]; // 最大数量
int dp[maxm]; // 朴素法一维数组
int main() {
int n,m;
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=m;j>=1;j--) {
for(int k=0;k<=s[i];k++) {
if(j>=k*v[i])
dp[j]=max(dp[j],dp[j-v[i]*k]+k*w[i]);
}
}
}
cout<<dp[m];
return 0;
}
二进制优化
即限制条件有两个,之前的背包是在物品总体积不超过背包容量的基础上使物品总价值最高,现在是:总体积不超过背包容量,总重量不超过背包可承受的最大重量。
二维费用背包
例题:洛谷 P1507
即限制条件有两个,之前的背包是在物品总体积不超过背包容量的基础上使物品总价值最高,现在是:总体积不超过背包容量,总重量不超过背包可承受的最大重量。
三维数组解法
时间复杂度O(n²),空间复杂度O(n³)
无非就是多了一个代价,所以两个条件同时满足的时候才可选取,状态转移方程的变化也异曲同工。
#include<bits/stdc++.h>
using namespace std;
const int maxh=400+4; // 体积最大值
const int maxt=400+4; // 质量最大值
const int maxn=50+5; // 物品最大数量
int h,t,n;
int dp[maxn][maxh][maxt];
int hh[maxn]; // 体积
int tt[maxn]; // 质量
int kk[maxn]; // 价值
int main() {
cin>>h>>t;
cin>>n;
for(int i=1;i<=n;i++) {
cin>>hh[i]>>tt[i]>>kk[i];
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=h;j++) {
for(int k=1;k<=t;k++) {
// j代表当前体积,k代表当前质量
if(j>=hh[i] && k>=tt[i]) {
dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-hh[i]][k-tt[i]]+kk[i]);
} else {
dp[i][j][k]=dp[i-1][j][k];
}
}
}
}
cout<<dp[n][h][t];
return 0;
}
二维数组解法
时间复杂度O(n³),空间复杂度O(n²)
利用压缩数组进行转移的时候,依然要逆向进行。变化不大。
#include<bits/stdc++.h>
using namespace std;
const int maxh=400+4; // 体积最大值
const int maxt=400+4; // 质量最大值
const int maxn=50+5; // 物品最大数量
int h,t,n;
int dp[maxh][maxt];
int hh[maxn]; // 体积
int tt[maxn]; // 质量
int kk[maxn]; // 价值
int main() {
cin>>h>>t;
cin>>n;
for(int i=1;i<=n;i++) {
cin>>hh[i]>>tt[i]>>kk[i];
}
for(int i=1;i<=n;i++) {
for(int j=h;j>=1;j--) {
for(int k=t;k>=1;k--) {
// j代表当前体积,k代表当前质量
if(j>=hh[i] && k>=tt[i]) {
dp[j][k]=max(dp[j][k],dp[j-hh[i]][k-tt[i]]+kk[i]);
}
}
}
}
cout<<dp[h][t];
return 0;
}