每周一算法:背包问题(五)求解方案数

01背包方案数

01背包求解方案数时,有下面两种情况:

  • 不超过背包容量求方案数
  • 恰好装满背包时求方案数

不超过背包容量求方案数

N N N件物品和一个容量是 M M M的背包。每件物品只能使用一次。第 i i i件物品的体积是 v i v_i vi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,求总方案数。

输入格式

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

第二行有 N N N 个整数 v i v_i vi用空格隔开,表示第 i i i 件物品的体积。

输出格式

输出一个整数,表示方案数。

样例输入

4 5
2 2 3 7

样例输出

7

提示

0 < N , M ≤ 1000 0<N,M≤1000 0<N,M1000

0 < v i ≤ 1000 0<v_i≤1000 0<vi1000

算法思想

4 4 4件物品,体积分别为 2 1 、 2 2 、 3 、 7 2_1、2_2、3、7 212237,在不超过背包容量 5 5 5的情况下,可以选择的方案有:

  • 0 0 0件物品, 1 1 1种方案。
  • 1 1 1件物品: 2 1 2_1 21 2 2 2_2 22 3 3 3,有 3 3 3种方案。
  • 2 2 2件物品: 2 1 / 2 2 2_1/2_2 21/22 2 1 / 3 2_1/3 21/3 2 2 / 3 2_2/3 22/3,有 3 3 3种方案。

一共有 7 7 7种方案。

求背包方案数可以通过递推计算。

状态表示

f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i件物品在不超过背包容量 j j j的情况下物品选择的方案数

状态计算

从最后一步分析,对于第 i i i件物品,只有两种情况:

  • 不选第 i i i件物品,方案数为前 i − 1 i-1 i1件物品在不超过背包容量 j j j的情况下物品选择的方案数 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]
  • 选择第 i i i件物品,方案数为前 i − 1 i-1 i1件物品在不超过背包容量 j − v i j-v_i jvi的情况下物品选择的方案数 f [ i − 1 ] [ j − v i ] f[i-1][j-v_i] f[i1][jvi]。该情况需要满足 v i ≤ j v_i\le j vij
初始状态

要求的是不超过背包容量 j j j的情况下物品选择的方案数,因此对于任意 j j j都有 f [ 0 ] [ j ] = 1 f[0][j]=1 f[0][j]=1,表示在不超过背包容量 j j j的情况下,不选择任何物品,方案数为 1 1 1

代码实现
#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[N][M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    for(int j = 0; j <= m; j ++) f[0][j] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j]; //不装第i件物品的方案数
            if(j >= v[i]) f[i][j] += f[i - 1][j - v[i]]; //装第i件物品的方案数
        }
    cout << f[n][m];
    return 0;
}

空间优化

基本思想可以参考博主的另一篇文章——每周一算法:背包问题(一)01背包

#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    for(int j = 0; j <= m; j ++) f[j] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = m; j >= v[i]; j --)
        {
            f[j] += f[j - v[i]]; //装第i件物品的方案数
        }
    cout << f[m];
    return 0;
}

恰好装满背包求方案数

N N N件物品和一个容量是 M M M的背包。每件物品只能使用一次。第 i i i件物品的体积是 v i v_i vi

求解将哪些物品装入背包,可使这些物品恰好装满背包,求总方案数。

输入格式

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

第二行有 N N N 个整数 v i v_i vi用空格隔开,表示第 i i i 件物品的体积。

输出格式

输出一个整数,表示方案数。

样例输入

4 5
2 2 3 7

样例输出

2

提示

0 < N , M ≤ 1000 0<N,M≤1000 0<N,M1000

0 < v i ≤ 1000 0<v_i≤1000 0<vi1000

算法思想

4 4 4件物品,体积分别为 2 1 、 2 2 、 3 、 7 2_1、2_2、3、7 212237恰好装满容量为 5 5 5的背包可以选择的方案有:

  • 2 2 2件物品: 2 1 / 3 2_1/3 21/3 2 2 / 3 2_2/3 22/3,共 2 2 2种方案。

一共有 2 2 2种方案。

求背包方案数可以通过递推计算。

状态表示

f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i件物品在恰好装满 j j j的情况下物品选择的方案数

状态计算

从最后一步分析,对于第 i i i件物品,只有两种情况:

  • 不选第 i i i件物品,方案数为前 i − 1 i-1 i1件物品在恰好装满 j j j的情况下物品选择的方案数 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]
  • 选择第 i i i件物品,方案数为前 i − 1 i-1 i1件物品在恰好装满 j − v i j-v_i jvi的情况下物品选择的方案数 f [ i − 1 ] [ j − v i ] f[i-1][j-v_i] f[i1][jvi]。该情况需要满足 v i ≤ j v_i\le j vij
初始状态

要求的是恰好装满容量 j j j的情况下物品选择的方案数,因此只有在 j = 0 j=0 j=0的情况下,有 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1,表示在没有选择任何物品、且背包容量为 0 0 0的情况下,方案数为 1 1 1

代码实现
#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[N][M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    f[0][0] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j]; //不装第i件物品的方案数
            if(j >= v[i]) f[i][j] += f[i - 1][j - v[i]]; //装第i件物品的方案数
        }
    cout << f[n][m];
    return 0;
}
空间优化

基本思想可以参考博主的另一篇文章——每周一算法:背包问题(一)01背包

#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    f[0] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = m; j >= v[i]; j --)
        {
            f[j] += f[j - v[i]]; //装第i件物品的方案数
        }
    cout << f[m];
    return 0;
}

小结

01背包方案数时,有下面两种情况,计算时的区别仅在于初始状态:

  • 不超过背包容量求方案数,初始状态 f [ 0 ] [ j ] = 1 , 0 ≤ j ≤ m f[0][j]=1, 0\le j\le m f[0][j]=1,0jm
  • 恰好装满背包时求方案数,初始状态 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1

完全背包方案数

完全背包(每种物品有无限件)求解方案数时,也有下面两种情况:

  • 不超过背包容量求方案数
  • 恰好装满背包时求方案数

不超过背包容量求方案数

N N N种物品和一个容量是 M M M的背包。每种物品都有无限件可用。第 i i i种物品的体积是 v i v_i vi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,求总方案数。

输入格式

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

第二行有 N N N 个整数 v i v_i vi用空格隔开,表示第 i i i 件物品的体积。

输出格式

输出一个整数,表示方案数。

样例输入

4 5
2 2 3 7

样例输出

9

提示

0 < N , M ≤ 1000 0<N,M≤1000 0<N,M1000

0 < v i ≤ 1000 0<v_i≤1000 0<vi1000

算法思想

4 4 4种物品,体积分别为 2 1 、 2 2 、 3 、 7 2_1、2_2、3、7 212237,在不超过背包容量 5 5 5的情况下,由于每种物品有无限件可选,那么选择的方案有:

  • 0 0 0件物品, 1 1 1种方案。
  • 1 1 1件物品: 2 1 2_1 21 2 2 2_2 22 3 3 3,有 3 3 3种方案。
  • 2 2 2件物品: 2 1 / 2 1 2_1/2_1 21/21 2 1 / 2 2 2_1/2_2 21/22 2 2 / 2 2 2_2/2_2 22/22 2 1 / 3 2_1/3 21/3 2 2 / 3 2_2/3 22/3,有 5 5 5种方案。

一共有 9 9 9种方案。

状态表示

f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i种物品在不超过背包容量 j j j的情况下物品选择的方案数

状态计算

从最后一步分析,对于第 i i i种物品,有下面几种情况:

  • 不选第 i i i种物品,方案数为前 i − 1 i-1 i1种物品在不超过背包容量 j j j的情况下物品选择的方案数 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]
  • 选择第 i i i种物品:
    • 选择 1 1 1件,方案数为前 i − 1 i-1 i1种物品在不超过背包容量 j − v i j-v_i jvi的情况下物品选择的方案数 f [ i − 1 ] [ j − v i ] f[i-1][j-v_i] f[i1][jvi]
    • 选择 2 2 2件,方案数为前 i − 1 i-1 i1种物品在不超过背包容量 j − 2 × v i j-2\times v_i j2×vi的情况下物品选择的方案数 f [ i − 1 ] [ j − 2 × v i ] f[i-1][j-2\times v_i] f[i1][j2×vi]
    • 选择最多 k k k件,方案数为前 i − 1 i-1 i1种物品在不超过背包容量 j − k × v i j-k\times v_i jk×vi的情况下物品选择的方案数 f [ i − 1 ] [ j − k × v i ] f[i-1][j-k\times v_i] f[i1][jk×vi]

因此, f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − v i ] + f [ i − 1 ] [ j − 2 × v i ] + f [ i − 1 ] [ j − 3 × v i ] + . . . + f [ i − 1 ] [ j − k × v i ] f[i][j]=f[i-1][j]+f[i-1][j-v_i]+f[i-1][j-2\times v_i]+f[i-1][j-3\times v_i]+...+f[i-1][j-k\times v_i] f[i][j]=f[i1][j]+f[i1][jvi]+f[i1][j2×vi]+f[i1][j3×vi]+...+f[i1][jk×vi]

f [ i ] [ j − v ] = f [ i − 1 ] [ j − v i ] + f [ i − 1 ] [ j − 2 × v i ] + f [ i − 1 ] [ j − 3 × v i ] + . . . + f [ i − 1 ] [ j − k × v i ] f[i][j-v]=f[i-1][j-v_i]+f[i-1][j-2\times v_i]+f[i-1][j-3\times v_i]+...+f[i-1][j-k\times v_i] f[i][jv]=f[i1][jvi]+f[i1][j2×vi]+f[i1][j3×vi]+...+f[i1][jk×vi]

那么, f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − v ] f[i][j]=f[i-1][j]+f[i][j-v] f[i][j]=f[i1][j]+f[i][jv]

初始状态

要求的是不超过容量 j j j的情况下物品选择的方案数,因此对于任意 j j j都有 f [ 0 ] [ j ] = 1 f[0][j]=1 f[0][j]=1,表示在不超过背包容量 j j j的情况下,不选择任何物品,方案数为 1 1 1

代码实现
#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[N][M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    for(int j = 0; j <= m; j ++) f[0][j] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j]; //不装第i种物品的方案数
            if(j >= v[i]) f[i][j] += f[i][j - v[i]]; //装第i种物品的方案数
        }
    cout << f[n][m];
    return 0;
}
空间优化

基本思想可以参考博主的另一篇文章——每周一算法:背包问题(二)完全背包

#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    for(int j = 0; j <= m; j ++) f[j] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = v[i]; j <= m; j ++)
        {
            f[j] += f[j - v[i]]; //装第i种物品的方案数
        }
    cout << f[m];
    return 0;
}

恰好装满背包求方案数

N N N种物品和一个容量是 M M M的背包。每种物品都有无限件可用。第 i i i种物品的体积是 v i v_i vi

求解将哪些物品装入背包,可使这些物品恰好装满背包,求总方案数。

输入格式

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

第二行有 N N N 个整数 v i v_i vi用空格隔开,表示第 i i i 种物品的体积。

输出格式

输出一个整数,表示方案数。

样例输入

4 5
2 2 3 7

样例输出

2

提示

0 < N , M ≤ 1000 0<N,M≤1000 0<N,M1000

0 < v i ≤ 1000 0<v_i≤1000 0<vi1000

算法思想

4 4 4种物品,体积分别为 2 1 、 2 2 、 3 、 7 2_1、2_2、3、7 212237恰好装满容量为 5 5 5的背包可以选择的方案有:

  • 2 2 2件物品: 2 1 / 3 2_1/3 21/3 2 2 / 3 2_2/3 22/3,共 2 2 2种方案。

一共有 2 2 2种方案。

状态表示

f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i种物品在恰好装满 j j j的情况下物品选择的方案数

状态计算

从最后一步分析,对于第 i i i种物品,有下面几种情况:

  • 不选第 i i i种物品,方案数为前 i − 1 i-1 i1种物品在恰好装满 j j j的情况下物品选择的方案数 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]
  • 选择第 i i i种物品:
    • 选择 1 1 1件,方案数为前 i − 1 i-1 i1种物品在恰好装满 j − v i j-v_i jvi的情况下物品选择的方案数 f [ i − 1 ] [ j − v i ] f[i-1][j-v_i] f[i1][jvi]
    • 选择 2 2 2件,方案数为前 i − 1 i-1 i1种物品在恰好装满 j − 2 × v i j-2\times v_i j2×vi的情况下物品选择的方案数 f [ i − 1 ] [ j − 2 × v i ] f[i-1][j-2\times v_i] f[i1][j2×vi]
    • 选择最多 k k k件,方案数为前 i − 1 i-1 i1种物品在恰好装满 j − k × v i j-k\times v_i jk×vi的情况下物品选择的方案数 f [ i − 1 ] [ j − k × v i ] f[i-1][j-k\times v_i] f[i1][jk×vi]

因此, f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − v i ] + f [ i − 1 ] [ j − 2 × v i ] + f [ i − 1 ] [ j − 3 × v i ] + . . . + f [ i − 1 ] [ j − k × v i ] f[i][j]=f[i-1][j]+f[i-1][j-v_i]+f[i-1][j-2\times v_i]+f[i-1][j-3\times v_i]+...+f[i-1][j-k\times v_i] f[i][j]=f[i1][j]+f[i1][jvi]+f[i1][j2×vi]+f[i1][j3×vi]+...+f[i1][jk×vi]

f [ i ] [ j − v ] = f [ i − 1 ] [ j − v i ] + f [ i − 1 ] [ j − 2 × v i ] + f [ i − 1 ] [ j − 3 × v i ] + . . . + f [ i − 1 ] [ j − k × v i ] f[i][j-v]=f[i-1][j-v_i]+f[i-1][j-2\times v_i]+f[i-1][j-3\times v_i]+...+f[i-1][j-k\times v_i] f[i][jv]=f[i1][jvi]+f[i1][j2×vi]+f[i1][j3×vi]+...+f[i1][jk×vi]

那么, f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − v ] f[i][j]=f[i-1][j]+f[i][j-v] f[i][j]=f[i1][j]+f[i][jv]

初始状态

要求的是恰好装满容量 j j j的情况下物品选择的方案数,因此只有在 j = 0 j=0 j=0的情况下,有 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1,表示在没有选择任何物品、且背包容量为 0 0 0的情况下,方案数为 1 1 1

代码实现
#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[N][M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    f[0][0] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j]; //不装第i种物品的方案数
            if(j >= v[i]) f[i][j] += f[i][j - v[i]]; //装第i种物品的方案数
        }
    cout << f[n][m];
    return 0;
}
空间优化

基本思想可以参考博主的另一篇文章——每周一算法:背包问题(二)完全背包

#include <iostream>
using namespace std;
const int N = 1010, M = 1010;
int v[N], f[M];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> v[i];
    //初始状态
    f[0] = 1;
    //状态计算
    for(int i = 1; i <= n; i ++)
        for(int j = v[i]; j <= m; j ++)
        {
            f[j] += f[j - v[i]]; //装第i种物品的方案数
        }
    cout << f[m];
    return 0;
}

小结

完全背包方案数时,也有下面两种情况,计算时的区别仅在于初始状态:

  • 不超过背包容量求方案数,初始状态 f [ 0 ] [ j ] = 1 , 0 ≤ j ≤ m f[0][j]=1, 0\le j\le m f[0][j]=1,0jm
  • 恰好装满背包时求方案数,初始状态 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1
  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值