背包九讲类型汇总:
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#include 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#include 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#include 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#include 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
思路分析
多重背包是选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#include 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
分析:O(n^3)的方法肯定会超时,需要(N^2*logN)的方法;
把si看做是一个二进制数,把si个可以拆成logsi个物体,这些物体肯定可以组成si个物体,转化成01背包问题;
#include#include 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#include 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]; }
5.二维费用的背包问题 Acwing 08
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
分析:在一维的基础上加一重循环即可;
#include#include using namespace std; int main(){ int N,M,V; cin >> N >> V >> M; vector<int> v(N+1),m(N+1),w(N+1); vector int>> dp(M+1,vector<int>(V+1)); for(int i = 1; i <= N; i++){ cin >> v[i] >> m[i] >> w[i]; } for(int i = 1; i <= N; i++){ for(int j = M; j >= m[i]; j--) for(int k = V;k >= v[i]; k--) //是否选第i个物品 dp[j][k] = max(dp[j][k],dp[j-m[i]][k-v[i]] + w[i]); } cout << dp[M][V]; }
6.分组背包问题 Acwing 09
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
思路:和多重背包有一些类似,多重背包是每个物品有si件,可以选0,1,2…si件。
而分组背包是不选,选第1个,或第2个或第3个…或第si个,都有si+1种决策方式,
即使用三层循环即可解决。没有优化方式。
#includeusing namespace std; int dp[104]; int w[104],v[104]; int main(){ int N,V,s; cin >> N >> V; for(int i = 1; i <= N; i++){ cin >> s; for(int j = 1; j <= s; j++) cin >> v[j] >> w[j]; for(int j = V; j >= 0;j--){ //从第i组中选一个 for(int k = 1; k <= s; k++){ if(j >= v[k]) dp[j] = max(dp[j],dp[j-v[k]]+w[k]); } } } cout << dp[V]; }
7.有依赖的背包问题 Acwing 10
有 N个物品和一个容量是 V 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如下图所示:
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。
每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
链式前向星还没学,邻接矩阵更方便:
#include#include #include using namespace std; #define N 104 int mp[N][N]; int dp[N][N]; int n,m; vector<int> v(N),w(N); void dfs(int root){ for(int i = 1; i <=n; i++){ //下一个子节点在不同空间下可以看成是一个组,求出这个组; if(mp[root][i]) dfs(i); else continue; //分组背包问题,在第i组中选一个最大的 for(int j = m-v[root]; j >=0;j--){//root一定要选,为root留一个位置 for(int k = 0; k <= j; k++) dp[root][j] = max(dp[root][j],dp[root][j-k]+dp[i][k]); } } //一定要选root,选不了root的话,就直接为0; for(int j = m; j >= 0; j--) if( j >= v[root]) dp[root][j] = dp[root][j-v[root]] + w[root]; else dp[root][j] = 0; } int main(){ memset(mp,0,sizeof mp); cin >> n >> m; int p, root; //用邻接矩阵储存树,并找到根节点 for(int i = 1;i <= n; i++){ cin >> v[i] >> w[i] >>p; if( p == -1) root = i; else mp[p][i] = 1; } dfs(root); cout << dp[root][m]; }
8.背包问题求方案数 Acwing 11
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7 的结果。
只需加一个数组,然后在01背包基础上修改即可:
#include#include #include 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的情况下的最大价值。 vector<int> p(V+1,1);//加一个数组表示方法数 int mod = 1e9+7; 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]);变为如下: { if(dp[j] < dp[j-v[i]]+w[i]){ dp[j] = dp[j-v[i]]+w[i]; p[j] = p[j-v[i]]; } else if(dp[j] == dp[j-v[i]]+w[i]){ p[j] += p[j-v[i]]; p[j] %= mod; } } cout <<p[V]; }
9.背包问题求具体方案 Acwing 12
一维数组似乎不能解决;
#include#include using namespace std; #define N 1004 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][N+1];////dp[i][j]表示前i个物品,背包容量是j的情况下的最大价值。 int p[N+1][N+1];//用来记录路径 memset(dp,0,sizeof(dp)); memset(p,0,sizeof p); //用字典序排序时,要从后往前遍历 for(int i = n; i >= 1; i--) for(int j = 1; j <= V; j++){ //在没有max时要假设先不选 dp[i][j] = dp[i+1][j];//这句容易忘,位置也要注意 if(v[i] <= j){ int temp = dp[i+1][j-v[i]] + w[i]; if(temp >= dp[i][j]){ dp[i][j] = temp; p[i][j] = 1; } } } //cout << dp[1][V] << endl; int vol = V; for(int i = 1; i <= n; i++){ if(p[i][vol] == 1){ cout << i << ' '; vol -= v[i]; } } }