[01背包] 背包问题求方案数(01背包+求方案数+求最优解方案数+思维)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[01背包] 背包问题求方案数(01背包+求方案数+求最优解方案数+思维)相关的知识,希望对你有一定的参考价值。
文章目录
0. 前言
相关:
强相关:
1. 01背包+求方案数+思维
本问题和 [01背包] 背包问题求具体方案(01背包+求方案数+思维) 求方案数不同。上个问题是求一条具体的最短路状态转移方案即可,因为会对字典序排序,所以当时我们采用了贪心的策略,当两个状态相等时,选小不选大。而本问题,这两个状态相等时,就把这两个都选上就行了。
这个问题代表了求最优解的方案数,也是代表了一大类问题,重要。而求解具体的一个方案,也是很重要的。并且一般让求方案的题,都不简单!
开辟一个新数组,g[i][j]
记录转移到 f[i][j]
的方案数是多少就行了,针对 f[i][j] = max(f[i-1][j], f[i-1][j-vi]+wi)
,对应着选 i
、不选 i
的两种情况。那么 g[i-1][j]
就是 f[i-1][j]
的方案数。g[i-1][j-vi]
就是 g[i-1][j-vi]
的方案数
- 当
f[i-1][j] < f[i-1][j-vi]+wi
的时候,g[i][j] = g[i-1][j]
- 当
f[i-1][j] > f[i-1][j-vi]+wi
的时候,g[i][j] = g[i-1][j-vi]
- 当
f[i-1][j] = f[i-1][j-vi]+wi
的时候,g[i][j] = g[i-1][j]+g[i-1][j-vi]
能够发现,f
和 g
都是仅和上一层有关系。所以都可以优化至一维。
在此,表示的是体积恰好是 j
的方案数。如果表示成体积最大是 j
的话计算起来会比较麻烦。因为在 01 背包中我们将体积定义为最大,实际上对于 f[m]
可能其并没有用到,但是仍旧能够转移过来。那么这个方案数就不好进行累加,因为相等的部分很多,得将这些无效转移剔除,所以比较麻烦。但是在此将状态转移与最大值精确对应起来,找到
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1005;
const int MOD = 1e9+7;
int n, m;
int f[N]; // f[j]是容积“恰好”为j时的最优解
int g[N]; // g[j]是容积“恰好”为j时的最优解的方案数
int main()
cin >> n >> m;
// 背包容量为0时,这些状态都不合法,此时状态的第二维表示的不是最多而是恰好
// 那么如果一件物品都没有,第二维的体积必须是0。
memset(f, -0x3f, sizeof f);
f[0] = 1;
g[0] = 1;
for (int i = 0; i < n; ++i)
int v, w;
cin >> v >> w;
for (int j = m; j >= v; --j)
int maxv = max(f[j], f[j - v] + w);
int cnt = 0;
if (maxv == f[j]) cnt += g[j];
if (maxv == f[j - v] + w) cnt += g[j - v];
g[j] = cnt % MOD; // 是f[j]等于maxv这个容积下的最优解方案数
f[j] = maxv; // 那么在不同容积下到达同样的最大值,最优解方案不同且需要累加
// 最终不一定占满全部背包体积,01背包在此仍会转移到f[m]
// 而在此由于方案数g[j]和f[j]严格对应,所以必须找到准确的取到最优解时的体积
int res = 0; // 这里状态定义是恰好,所有需要遍历求解最大值
for (int i = 0; i <= m; ++i) res = max(res, f[i]);
int cnt = 0; // 最大值可能由不同容积下都能达到,所以只要f[i]与res相等,g[i]就相加
for (int i = 0; i <= m; ++i)
if (res == f[i])
cnt = (cnt + g[i]) % MOD;
cout << cnt << endl;
return 0;
以下为 AcWing 讨论区的看到的:
滑稽聚聚:AcWing 11. 背包问题求方案数 的题解,很简洁的另一种状态定义方式,和下面这个是同样的思路。
不定义体积为恰好时,也是可以计算的:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010, mod = 1e9+7;
int n, m;
int f[N], g[N];
int v[N], w[N];
int main()
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
for (int i = 0; i <= m; i ++ ) g[i] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
int left = f[j], right = f[j - v[i]] + w[i];
f[j] = max(left, right);
if (left > right) g[j] = g[j];
else if (left < right) g[j] = g[j - v[i]];
else g[j] = g[j] + g[j - v[i]];
g[j] %= mod;
cout << g[m] << endl;
return 0;
为什么这里 g[j] = g[j]
和 g[j] = g[j - v[i]]
不是 +=
呢?在此就是左边大的话就是取左面,右边大取右边。
可理解为: 当前这个状态如果能由上一层的某个状态转移过来的了的话,就说明之前更新这个状态的点不是最优的,那么我们就不能再将其算作一种选法了,所以就不能是 +=
,而必须是直接赋值
相当于有两个集合,一个集合选当前的物品,一个集合不选。然后我们要求的是两个集合的最大值,以及最大值的元素数。那么如果左边集合的最大值大于右边集合的最大值,那最大值的元素数就等于左边集合最大值的元素数(在此,就不是 += 了);反之就是右边集合的元素数;如果左右两边最大值相等,那么就应该是左右两个集合中取最大值的元素的个数之和。
2020/11/24
先加入疑问题单,能理解本题体积做法,但无法做过多拓展。体会体会!
以上是关于[01背包] 背包问题求方案数(01背包+求方案数+求最优解方案数+思维)的主要内容,如果未能解决你的问题,请参考以下文章
[H背包] lc1449. 数位成本和为目标值的最大数字(背包求具体方案+状态定义+边界情况)
P1466 集合 Subset Sums(01背包求填充方案数)