image.png
0—-1背包是每个物品只有一个
完全背包,每个物品有无数个
多重背包:有多种物品,每个物品有多少个,都是告诉的????
分组背包是每类物品,比如苹果,橘子,香蕉只能选择一个;西红柿,菠菜,土豆只能选一个

一:状态表示和状态计算

  • 状态表示

image.png
状态表示一句话概括:前i个物品中总体积小于等于j 的各种选法的集合,他存的数是每一种选法的价值的最大值

  • 状态计算

班级分数的例子
image.png

  • 注意状态计算的时候,右边含有i的情况,很有可能由于i 的体积大于j导致右边情况是不存在,对于左边情况是存在的,所以不用担心。

二维的做法

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. const int N = 1010;
  5. int f[N][N], w[N], v[N];
  6. int n, m;
  7. int main()
  8. {
  9. ios::sync_with_stdio(false);
  10. cin >> n >> m;
  11. for (int i = 1; i <= n; i++) // i从1开始需要注意,因为f是前i个物品的最大值,前后对应
  12. {
  13. cin >> v[i] >> w[i];
  14. }
  15. for (int i = 1; i <= n; i++) // 前0个物品没有意义
  16. for (int j = 1; j <= m; j++) // 背包体积为0也是没有意义
  17. {
  18. f[i][j] = f[i - 1][j];
  19. if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]); // 一定注意判断
  20. }
  21. cout << f[n][m] << endl;
  22. return 0;
  23. }

一维的优化

这里一点也不理解一维的做法

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

二:完全背包问题

image.png

朴素算法:

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

优化状态转移方程

image.png

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

状态方程一维的优化

image.png

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

最少的硬币数量

三:多重背包问题

暴力做法:

和上面的完全背包类似,就是加了一个s[i],第三层循环加了一个判断条件,k <= s[i]。

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

多重背包的二进制优化.

  • 多重背包问题不能像完全背包进行优化,多了后面一项的同时,不能表示简单的f[i - 1, j - v] + w来表示

image.png
下面的两个图,意思就是s个第i个物品,可以拆分成多组,便曾0,1背包,因为多个组合随意组合就能凑出0-s的所有的数,时间复杂度就是下面 第三个图,叫做多重背包的二进制优化.
image.png
2.
image.png
3.
image.png

  • 多重背包的优化就是利用二进制进行变换,变成01背包问题,一共两步。
  • 其实本质就是把第i给物品的s[i]个,二进制压缩成logs个物品,这些个物品的体积和价值都不相同,所以可以看作一个新的物品,然后用01背包问题去解决。 ```cpp

    include

    include

using namespace std;

const int N = 25000;

int f[N], v[N], w[N]; int n, m;

int main() { ios::sync_with_stdio(false); cin >> n >> m; int cnt = 0; // 新数组的 for (int i = 1; i <= n; i++) // 构造01背包 { int k = 1; int a, b, c; cin >> a >> b >> c; while (k <= c) { ++ cnt; v[cnt] = a k; w[cnt] = b k; c -= k; k = 2; } if (c > 0) { cnt ++; v[cnt] = a c; w[cnt] = b * c; } } n = cnt; for (int i = 1; i <= n; i++) // 01背包的解决 for (int j = m; j >= v[i]; j—) f[j] = max(f[j], f[j - v[i]] + w[i]); cout << f[m] << endl; return 0; }

  1. <a name="FXOza"></a>
  2. # 四:分组背包问题
  3. - 如果使用上一层的状态的时候,变成一维的时候,就需要从大倒下到小转换进行枚举,否则用到本层(完全背包)则需要从小到大。因为从大到小,算这个体积的时候,上一个体积还没有计算过,所以存的是上一个体积的状态
  4. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/312760/1629277093520-32cc0f49-585b-4281-b0a5-27e59e6010df.png#clientId=u5552a419-41d1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=400&id=ud975360a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=799&originWidth=1622&originalType=binary&ratio=1&rotation=0&showTitle=false&size=424392&status=done&style=none&taskId=ua91318a2-b569-4964-bcb4-d2c9cec0dc6&title=&width=811)<br />明显可以得到分组背包问题使用了前一个状态。所以转换成一维的时候,就得考虑从大到小的方式遍历。
  5. ```cpp
  6. #include <iostream>
  7. #include <algorithm>
  8. using namespace std;
  9. const int N = 110;
  10. int f[N];
  11. int v[N][N], w[N][N], s[N];
  12. int n, m;
  13. int main()
  14. {
  15. ios::sync_with_stdio(false);
  16. cin >> n >> m;
  17. for (int i = 1; i <= n; i++)
  18. {
  19. cin >> s[i];
  20. for (int j = 1; j <= s[i]; j++)
  21. {
  22. cin >> v[i][j] >> w[i][j];
  23. }
  24. }
  25. for (int i = 1; i <= n; i++)
  26. for (int j = m; j >= 0; j--) // 上面已经分析过了,没有>=v[i]因为i是组,体积判断通过if
  27. for (int k = 0; k <= s[i]; k++)
  28. if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]); // 不能把这里的if放到上面,会导致第三层循环没有遍历完就结束
  29. cout << f[m] << endl;
  30. return 0;
  31. }