[01背包] 背包问题求方案数(01背包+求方案数+求最优解方案数+思维)

23 篇文章 1 订阅
13 篇文章 0 订阅

0. 前言

相关:

强相关:

1. 01背包+求方案数+思维

11. 背包问题求方案数

在这里插入图片描述
本问题和 [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]

能够发现,fg 都是仅和上一层有关系。所以都可以优化至一维。

在此,表示的是体积恰好是 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
先加入疑问题单,能理解本题体积做法,但无法做过多拓展。体会体会!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值