背包问题求方案数

题目 背包问题求方案数

image-47c1a00b

思路分析

做这道题之前先写一下 数字组合

数字组合这题问的是所有方案数 就是简单的把原来集合的max属性变成了count属性

而这道题要我们求的是 最大价值的方案数

实际上就是把原本的01背包(求最大价值)和这个变形的01背包(求方案数)做一个结合

在同一dp过程中 同时把这两件事做了即可

思路

  • f[i][j]记录考虑前i件物品,当前背包容量为j时的最大价值。
  • g[i][j]记录在考虑前i件物品,当前背包容量为j时,达到最大价值f[i][j]的方案数。
  1. 初始化:f[0][0]不需要管 默认为0 从前0个物品中选体积不超过0的物品的总价值的最大值自然是0 g[i][0] = 1无论考虑多少物品 总存在一种方案使得容量为0的背包达到价值0 即不选择任何物品
  2. 动态规划更新:
  • 遍历物品i从1到n,和背包容量j0m
  • 更新f[i][j]:如果不取当前物品,则价值为f[i-1][j];如果取当前物品(前提是j大于等于物品体积v[i]),则价值为f[i-1][j-v[i]] + w[i],取两者的较大值。
  • 更新g[i][j]
    • 如果f[i][j]的值来源于f[i-1][j](即不取当前物品),则方案数为g[i-1][j]
    • 如果f[i][j]的值来源于f[i-1][j-v[i]] + w[i](即取当前物品),则方案数为g[i-1][j-v[i]]
    • 注意,如果f[i][j]同时满足上述两种情况,即f[i][j]的值既可以通过不取当前物品也可以通过取当前物品得到,那么g[i][j]应该累加这两种情况的方案数。
  1. 最后,遍历j从0到m,累加所有f[n][j]等于f[n][m](即最大价值)时的g[n][j]值,得到的总和就是最终的方案数。

问题:

为什么累加所有f[n][j]等于f[n][m](即最大价值)时的g[n][j]值,得到的总和就是最终的方案数? 我们知道g[n][j]的含义是从前n个物品当中选 体积不超过j的所有达到最大价值的方案数 如果某个方案只选到了k(k是小于n的)就已经达到了最大价值f[n][m] 那它还需要表示为前n个物品中选吗 或者换句话说 所疑惑的这种情况 是否被包含进了g[n][0-m]当中?比如达到最大价值 此时只在k个物品中选了3 即g[k][3] 好像g[n][3]也包括了这个g[k][3]?

分析:

动态规划的状态定义和转移

在0-1背包问题中,状态f[i][j]表示考虑前i个物品,当前背包容量为j时的最大价值。状态g[i][j]记录的是达到这个最大价值的方案数。这里的重点是"考虑前i个物品"并不意味着必须选择第i个物品,也不意味着必须恰好选i个物品。它意味着在前i个物品中进行选择,不超过容量j的条件下可以达到的最大价值及其方案数。

考虑不同数量的物品

当我们说g[n][j]包括了g[k][3](对于某个k < n和某个容量3),实际上我们是在说:在计算g[n][j]时,我们考虑了所有从第1个物品到第n个物品的可能组合,其中包括了那些仅使用前k个物品达到某个价值的所有方案。

如何理解“考虑前n个物品”中包含了更少物品的情况

动态规划的过程是累积的。当我们在计算f[i][j]和g[i][j]的值时,我们是基于之前所有的计算结果来的。这意味着,如果存在一个最优的方案,它实际上只选取了前k个物品中的一部分,那么这个方案在计算f[k][x]和g[k][x]时已经被考虑过,并且它的价值和方案数被递推到了f[n][j]和g[n][j]。

这是因为,当我们从k递推到n时,如果后面的物品没有被选择(即它们不增加总价值),fg的值仍然会保留那个最大价值和对应的方案数,因为我们在动态规划中是通过比较和选择最大值来更新状态的。

结论

因此,即使某个最优方案实际上只选择了前k个物品中的一些,这个方案仍然会被包含在最终的g[n][j]中,因为在递推过程中,我们考虑了所有可能的物品组合,包括那些在中途就已经达到最大价值的方案。这就是动态规划的美妙之处:它通过局部最优解的累积,最终得到全局最优解,并能够统计达到这个全局最优解的所有可能的方案数。

代码

现在对于f[][]和g[][]的理解应该有一些了吧

那么再考虑最后一个问题

f[n][m]毋庸置疑是最大答案 我们01背包取最大价值就是取它

根据前面的分析 f[n][0-m]显然就是其他选法的总价值 它对应的选法数量都在g[n][0-m]当中

那么只要发现f[n][j]里有和我们最大价值f[n][m]相等的值 就把那些选法数量(g[n][j])累加起来

朴素:

#include<bits/stdc++.h>
using namespace std;

const int N = 1010, mod = 1e9 + 7;
int v[N], w[N];
int f[N][N], g[N][N];
int n, m;

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> v[i] >> w[i];

    // 初始化方案数为1,即不选任何物品的情况
    for (int i = 0; i <= n; i++)
        g[i][0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            f[i][j] = f[i-1][j]; // 不选第i个物品
            g[i][j] = g[i-1][j]; // 继承不选的方案数

            if (j >= v[i]) {
                if (f[i][j] < f[i-1][j-v[i]] + w[i]) {
                    f[i][j] = f[i-1][j-v[i]] + w[i]; // 更新最大价值
                    g[i][j] = g[i-1][j-v[i]]; // 更新方案数
                } else if (f[i][j] == f[i-1][j-v[i]] + w[i]) {
                    g[i][j] = (g[i][j] + g[i-1][j-v[i]]) % mod; // 累加方案数
                }
            }
        }
    }

    int res = 0;
    for (int j = 0; j <= m; j++) {
        if (f[n][j] == f[n][m]){
            res = (res + g[n][j]) % mod;
        }
    }
    cout << res;

    return 0;
}

接着就是老套路 消掉一维

#include<bits/stdc++.h>
using namespace std;

const int N = 1010, mod = 1e9 + 7;
int v[N], w[N];
int f[N], g[N];
int n, m;

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> v[i] >> w[i];

    g[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = m; j >= v[i]; j--) {
            if (f[j] < f[j-v[i]] + w[i]) {
                f[j] = f[j-v[i]] + w[i]; // 更新最大价值
                g[j] = g[j-v[i]]; // 更新方案数
            } else if (f[j] == f[j-v[i]] + w[i]) {
                g[j] = (g[j] + g[j-v[i]]) % mod; // 累加方案数
            }
        }
    }

    int res = 0;
    for (int j = 0; j <= m; j++) {
        if (f[j] == f[m]){
            res = (res + g[j]) % mod;
        }
    }
    cout << res;

    return 0;
}

我的这种理解方式是和y总以及其他题解不太一样的 但是和铅笔哥是差不多思路

感觉要比什么更改状态从不超过j变为恰好等于j要容易理解些

代码实现

#include<bits/stdc++.h>

using namespace std;

const int N=1010,mod=1e9+7;

int v[N],w[N];

int f[N][N],g[N][N];

int n,m;

int main()

{

    cin>>n>>m;

    for(int i=1;i<=n;i++)

        cin>>v[i]>>w[i];

    for(int i=0;i<=n;i++)

        g[i][0]=1;

    for(int i=1;i<=n;i++){

        for(int j=0;j<=m;j++){

            f[i][j]=f[i-1][j];

            g[i][j]=g[i-1][j];

            if(v[i]<=j){

                if(f[i][j]<f[i-1][j-v[i]]+w[i]){

                    f[i][j]=f[i-1][j-v[i]]+w[i];

                    g[i][j]=g[i-1][j-v[i]];

                }

                else if(f[i][j]==f[i-1][j-v[i]]+w[i])

                    g[i][j]=(g[i][j]+g[i-1][j-v[i]])%mod;

            }

        }

    }

    int res=0;

    for(int j=0;j<=m;j++)

        if(f[n][j]==f[n][m])

            res=(res+g[n][j])%mod;

    cout<<res;

    return 0;

}
#include<bits/stdc++.h>

using namespace std;

const int N=1010,mod=1e9+7;

int v[N],w[N];

int f[N],g[N];

int n,m;

int main()

{

    cin>>n>>m;

    for(int i=1;i<=n;i++)

        cin>>v[i]>>w[i];

    g[0]=1;

    for(int i=1;i<=n;i++){

        for(int j=m;j>=v[i];j--){

            if(f[j]<f[j-v[i]]+w[i]){

                f[j]=f[j-v[i]]+w[i];

                g[j]=g[j-v[i]];

            }

            else if(f[j]==f[j-v[i]]+w[i])

                g[j]=(g[j]+g[j-v[i]])%mod;

        }

    }

    int res=0;

    for(int j=0;j<=m;j++)

        if(f[j]==f[m])

            res=(res+g[j])%mod;

    cout<<res;

    return 0;

}

同类题型

视频讲解


⬅️ 背包问题求具体方案数 🏠 00-刷题理模型 ➡️ 跳台阶