当前位置:   article > 正文

❤️详解经典动态规划算法「背包问题」_动态规划背包问题算法分析

动态规划背包问题算法分析

目录

1-01背包

2.完全背包

3.多重背包问题

4.分组背包问题


1-01背包

问题描述

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8

01背包问题是背包问题的核心思想,理解01背包,以外的背包问题自然迎刃而解

解题思路

1.定义 dp[N][N] 二维数组。 dp[i][j] 表示:只考虑前 i 个物品,背包容量在j的情况下的最大总价值

2.那么题目所求的答案就是  dp[n][v] :考虑n个物品,背包容量为v的情况下的最大总价值

3.由动态规划的思想,将大问题转化为许多子问题,由子问题层层递推到最终问题的答案。递推的过程关键是得出状态转移方程。 那么 分析得到 dp[i][j]可以由 dp[i-1][j-vi] 得到。

状态转移方程为:dp[i][j]=max(dp[i-1][j],dp[i-1][j-vi]+w[i])

含义:dp[i-1][j] 则是 不选第i个物品的情况下 只考虑前i-1个物品的价值 。

          dp[i-1][j-vi]+w[i] 则是 选第i个物品,那么总容量就要减去第i个物品的体积v[i] ,再去考虑前i-1个物品时的最大价值。 选第i个物品,总价值则要加上第i个物品的价值w[i]

4.由前面的定义知 dp[0][0]=0。表示考虑0个物品 容量为0 那么总价值一定为0, 从dp[0][0]开始递推 在容量为 1~ j 的情况下 考虑是否选 第 1个物品 ,依次类推 到 在1~j的容量下 考虑是否选第 n个物品 ,取 选 或者不选 情况下的价值最大情况。

代码如下:

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int N=1010;
  5. int dp[N][N];
  6. int v[N],w[N],n,m;
  7. int main()
  8. {
  9. cin>>n>>m;
  10. for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  11. for(int i=1;i<=n;i++)
  12. for(int j=0;j<=m;j++)
  13. {
  14. if(j>=v[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
  15. else dp[i][j]=dp[i-1][j];
  16. }
  17. cout<<dp[n][m]<<endl;
  18. return 0;
  19. }

5.对空间进行优化 由上面的递推关系可知:每一层的答案都是由上一层推来的,和上上一层没有关系,那么就可以去掉一维,用一维度的滚动数组来实现以上功能

状态转移方程简化为:dp[j]=max(dp[j],dp[[j-vi]+w[i])

代码如下:

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int N=1010;
  5. int n,m,dp[N];
  6. int v[N],w[N];
  7. int main()
  8. {
  9. cin>>n>>m;
  10. for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  11. for(int i=1;i<=n;i++)
  12. for(int j=m;j>=v[i];j--)
  13. dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
  14. cout<<dp[m]<<endl;
  15. return 0;
  16. }

2.完全背包

题目描述

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

1.完全背包问题思路 和 01背包大同小异,差别在于每一个物品可以取无数次 直到取到 容量不够的情况下为止。

状态转移方程:dp[i][j] = max(dp[i][j],dp[i][j-k*v[i]]+k*w[i])  (k从0开始)

代码如下:

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int N=1010;
  5. int n,m,v[N],w[N];
  6. int dp[N][N];
  7. int main()
  8. {
  9. cin>>n>>m;
  10. for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  11. for(int i=1;i<=n;i++)
  12. for(int j=0;j<=m;j++)
  13. for(int k=0;k*v[i]<=j;k++)
  14. dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
  15. cout<<dp[n][m]<<endl;
  16. return 0;
  17. }

2.跟01背包一样 进行 空间优化 dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i])

代码如下:

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int N=1010;
  5. int n,m,v[N],w[N];
  6. int dp[N];
  7. int main()
  8. {
  9. cin>>n>>m;
  10. for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  11. for(int i=1;i<=n;i++)
  12. for(int j=m;j>=0;j--)
  13. for(int k=0;k*v[i]<=j;k++)
  14. dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
  15. cout<<dp[m]<<endl;
  16. return 0;
  17. }

3. 再次分析可知 考虑第i个物品时,当第二次拿这个物品,相当于 在第一次拿这个物品的情况又拿了一次,第三次拿则相当于在第二次拿的情况下再拿一次。

则状态转移方程为: dp[j] = max(dp[j],dp[j-v[i]]+w[i])

代码如下:

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int N=1010;
  5. int n,m,v[N],w[N];
  6. int dp[N];
  7. int main()
  8. {
  9. cin>>n>>m;
  10. for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  11. for(int i=1;i<=n;i++)
  12. for(int j=v[i];j<=m;j++)
  13. dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
  14. cout<<dp[m]<<endl;
  15. return 0;
  16. }

3.多重背包问题

题目描述

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

1.多重背包可以按完全背包的情况进行考虑 ,每一个物品都考虑 拿 1~k 次,从中取最大值保存

2.因为物品数量有限, 因此遍历1~j的容量大小时,物品数并不一定是递增的(因为物品数有限,可能已经没了)

朴素代码如下:

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int N=110;
  5. int n,m,dp[N];
  6. int v[N],w[N],s[N];
  7. int main()
  8. {
  9. cin>>n>>m;
  10. for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
  11. for(int i=1;i<=n;i++)
  12. for(int j=m;j>=0;j--)
  13. for(int k=0;k<=s[i] && k*v[i]<=j;k++)
  14. {
  15. dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
  16. }
  17. cout<<dp[m]<<endl;
  18. return 0;
  19. }

3.但这种代码有三层循环,时间复杂度较高,可以使用是二进制优化法进行优化

思路:将每一个物品 的数量按 2^1 2^2 2^3 ...[log k], k-前项和。 这些分组正好可以组合成 第 i个物品 分别取1 ~ k 次的方案。那么就可以将此类背包问题转化为 0 1 背包问题了。

代码如下:

  1. #include<iostream>
  2. #include<vector>
  3. #include<algorithm>
  4. using namespace std;
  5. typedef pair<int,int> PI;
  6. const int N=10010;
  7. int n,m,dp[N];
  8. vector<PI> goods;
  9. int main()
  10. {
  11. cin>>n>>m;
  12. while(n--)
  13. {
  14. int v,w,s;
  15. cin>>v>>w>>s;
  16. for(int i=1;s>=i;i<<=1)
  17. {
  18. goods.push_back({v*i,w*i});
  19. s-=i;
  20. }
  21. if(s>0) goods.push_back({s*v,s*w});
  22. }
  23. for(auto i: goods)
  24. for(int j=m;j>=i.first;j--)
  25. {
  26. dp[j]=max(dp[j],dp[j-i.first]+i.second);
  27. }
  28. cout<<dp[m]<<endl;
  29. return 0;
  30. }

4.分组背包问题

问题描述

有 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
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8

 1.解题思路,分组背包问题比较简单,解题思路就是 从每一个组中选物品 ,取选法的最大值,依次递推下去,如果把每一个组想象成一个物品,那么就是01背包问题了。

代码如下:

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int N=110;
  5. int n,m,dp[N];
  6. int v[N][N],w[N][N],s[N];
  7. int main()
  8. {
  9. cin>>n>>m;
  10. for(int i=1;i<=n;i++)
  11. {
  12. cin>>s[i];
  13. for(int j=0;j<s[i];j++)
  14. {
  15. cin>>v[i][j]>>w[i][j];
  16. }
  17. }
  18. for(int i=1;i<=n;i++)
  19. for(int j=m;j>=0;j--)
  20. for(int k=0;k<s[i];k++)
  21. if(j>=v[i][k]) dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
  22. cout<<dp[m]<<endl;
  23. return 0;
  24. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/507049
推荐阅读
相关标签
  

闽ICP备14008679号