背包九讲类型汇总:
1.01背包问题
2.完全背包问题
3.多重背包问题
4.混合背包问题
5.二维费用的背包问题
6.分组背包问题
7.有依赖的背包问题
8.背包问题求方案数
9.求背包问题的具体方案
1. 01背包问题 Acwing 02
有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。
第 i件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
朴素版解法:二维空间解法
每件物品只能选一次,对于每种物品,我们有两种选择
1.不选 -> dp[i][j]=dp[i-1][j]
等于选前i-1个物品,空间为j情况下的最优解
2.选 -> dp[i][j]=dp[i-1][j-v[i]]+w[i]
如果选的话,前i-1个物品的体积最多为j-v[i]
#include<iostream> #include<cstring> using namespace std; int main(){ int n, V; cin >> n >> V; int v[n+1],w[n+1]; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; int dp[n+1][V+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。 memset(dp,0,sizeof(dp)); for(int i = 1;i <=n; i++) for(int j = 1; j <= V; j++) if(v[i] <= j) dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]); else dp[i][j] = dp[i-1][j];//这句容易忘 cout <<dp[n][V]; }
解法二:滚动数组优化:(实际上只需要一个数组)
#include<iostream> #include<cstring> using namespace std; int main(){ int n, V; cin >> n >> V; int v[n+1],w[n+1]; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; int dp[V+1];////dp[j]表示背包容量是j的情况下的最大价值。 memset(dp,0,sizeof(dp)); for(int i = 1;i <=n; i++) for(int j = V;j>= v[i];j--) dp[j] = max(dp[j],dp[j-v[i]]+w[i]); cout <<dp[V]; }
注:若题目要求装满背包,即将物品恰装入一个容量为m的背包中,只需要将初始化条件改一改即可,----将dp数组初始化为负无穷,dp[0]=0,即可确保状态一定是从0转移过来的。
2.完全背包问题 Acwing 03
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
朴素版解法:二维空间解法
也是两种选择,选或不选,只不过每个物品可以选无限次,在01的基础上把
dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i])
改为
dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]) (dp[i][j-v[i]] 可能已经是选过第i个的了)即可
#include<iostream> #include<cstring> using namespace std; int main(){ int n, V; cin >> n >> V; int v[n+1],w[n+1]; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; int dp[n+1][V+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。 memset(dp,0,sizeof(dp)); for(int i = 1;i <=n; i++) for(int j = 1; j <= V; j++) if(v[i] <= j) dp[i][j] = max(dp[i-1][j],dp[i][j-v[i]]+w[i]); //注意 else dp[i][j] = dp[i-1][j]; cout <<dp[n][V]; }
优化空间版解法:
转移方程为dp[j]=max(dp[j],dp[j-v[i]]+w[i])
#include<iostream> #include<cstring> using namespace std; int main(){ int n,m; cin >> n >>m; int v[n+1],w[n+1]; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; int dp[m+1]; memset(dp,0,sizeof(dp)); for(int i = 1; i <= n; i++) for(int j = v[i]; j <= m; j++)//注意 dp[j] = max(dp[j],dp[j-v[i]]+w[i]); cout << dp[m]; }
3.多重背包问题
方法1:o(n^3)做法 Acwing 04
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
数据范围
0<N,V≤1000<N,V≤100
0<vi,wi,si≤100
思路分析
多重背包是选0个,1个,2个…s[i]个
即dp[j]=max(dp[j],dp[j - v[i] * k]+w[i] * k)
k=1,2,3,…s[i]
那么再加一层循环表示选多少个就可以了
#include<iostream> #include<cstring> using namespace std; int main(){ int v[104],s[104],w[104]; int n, m; cin >> n >> m; for(int i = 1; i <=n;i++) cin >> v[i] >> w[i] >> s[i]; int dp[104]; memset(dp,0,sizeof(dp)); for(int i=1; i <= n; i++) for(int j = m; j >= v[i];j--){ for(int k = 1; j-k*v[i]>= 0 && k <= s[i]; k++)//注意条件 dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i]); } cout << dp[m]; }
方法2:二进制优化做法 Acwing 05
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
数据范围:
0<N≤10000<N≤1000
0<V≤20000<V≤2000
0<vi,wi,si≤2000
分析:O(n^3)的方法肯定会超时,需要(N^2*logN)的方法;
把si看做是一个二进制数,把si个可以拆成logsi个物体,这些物体肯定可以组成si个物体,转化成01背包问题;
#include<iostream> #include<vector> using namespace std; #define N 2004 int v[N],w[N],s[N]; int main(){ vector<int> vv = {0}; vector<int> ww = {0}; 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 k = 1; k <= s[i];k*=2){ vv.push_back(k*v[i]); ww.push_back(k*w[i]); s[i] -= k; } if(s[i]) { vv.push_back(s[i]*v[i]); ww.push_back(s[i]*w[i]); } } //新物品的个数 n = vv.size()-1; //常规01背包问题 vector<int> dp(m+1); for(int i = 1; i<= n; i++){ for(int j = m; j >= vv[i];j--){ dp[j] = max(dp[j],dp[j-vv[i]]+ww[i]); } } cout << dp[m]; }
题目3:多重背包终极版… Acwing 06
题目跟上面一样,但是数据范围如下
4.混合背包问题 Acwing 07
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
#include<iostream> #include<vector> using namespace std; #define N 1004 int main(){ vector<int> v(N),w(N),s(N); int m,n; cin >> n >> m; for(int i = 1; i <= n; i++){ cin >> v[i] >> w[i] >> s[i]; if(s[i] == -1) s[i] = 1;//01背包相当于只能选一次的多重背包 } vector<int> dp(N); for(int i = 1; i <= n; i++){ if(s[i] == 0){//可以选无数次 for(int j = v[i];j <= m; j++) dp[j] = max(dp[j],dp[j-v[i]]+w[i]); } else if(s[i] > 0){//多重背包问题,二进制优化,优化时直接计算 for(int k = 1; k <= s[i]; k*=2){ s[i] -= k; for(int j = m; j >= k*v[i];j--) dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i]); } } //多重背包最后一个,或者01背包 for(int j = m; j >= s[i]*v[i];j--) dp[j] = max(dp[j],dp[j-s[i]*v[i]]+s[i]*w[i]); } cout << dp[m]; }