动态规划 - 数字和为sum的方法数

问题描述:

给定一个有n个正整数的数组A和一个整数sum,求选择数组A中部分数字和为sum的方案数。

当两种选取方案有一个数字的下标不一样,我们就认为是不同的组成方案。

输入描述:

输入为两行: 第一行为两个正整数n(1 ≤ n ≤ 1000),sum(1 ≤ sum ≤ 1000)第二行为n个正整数A[i](32位整数),以空格隔开。

输出描述:

输出所求的方案数

 

示例1

输入

5 15

5 5 10 2 3

输出

4

这道题是一道动态规划的背包,我们以上面的输入输出为例

 

首先,将输入的数据放到一个一维数组里,我这里使用的是vector,都一样

注意: 下标为0的位置跳过,这个位置默认值为0,我们的数据从下标为1的位置开始

下标

0

1

2

3

4

5

v[i]

0

5

5

10

2

3

 

接下来我们构建一个二维数组,由于这个题目给出的n,sum的范围都是 1000,于是我们构建一个 1001*1001的二维数组dp

注意: 这个数组不要构建在栈上,这个空间可能很大,这里要求的是1000,有的题目会要求5000,难道要在栈上构建一个 5001*5001*sizeof(int) 大小的空间吗

所以这里建议将这个数组定义在全局区或者在对上动态开辟,不过我觉得在全局区上使用起来比较简单

p[i]

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0

0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

10

3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

4

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

5

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 蓝色的一列表示当前拥有质量为多少的物品,

  • 红色的一行表示当前所需要合成的背包质量是多少

举一个例子:

比如图中黄色的点,他的坐标为 (2,2), 表示的意思为我们现在拥有质量为 v[0](也就是0)的物品和质量为v[1](也就是5)的物品 (注意,这里只要小于等于当前行的物品我们都可以看作是拥有的),我们需要组合成一个质量为5(也就是当前列的下标)的背包,黄色格子处填的就是方法数

第一步: dp的第一列全部赋值为1


p[i]

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0

0

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

1

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

2

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

10

3

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

4

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

5

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

我们可以理解为

  • 第0行: 我们有质量为0物品,若要构成一个质量为0的背包,只有一种方法

  • 第1行: 我们有了质量为0的物品和质量为5的物品,若要构成一个质量为0的背包,也是只有一种方法

  • ...

  • 第5行: 我们分别有了质量为 0,5,5,10,2,3的物品,但是还是构成一个质量为0的背包,实则还是只有一种方法

 

第二步: dp的第一行全都赋值为0(除了dp[0][0])


p[i]

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0

0

1

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

5

1

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

2

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

10

3

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

4

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

5

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

可以理解为

  • 第1列: 只有质量为0的物品,要构成质量为1的背包,有0种方法

  • 第2列: 只有质量为0的物品,要构成质量为2的背包,有0种方法

  • ...

  • 第10列: 只有质量为0的物品,要构成质量为15的背包,有0种方法

 

第三步: 进入循环,给剩余格子 进行赋值


循环条件:

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

{

    for(int i = 1;i <= sum; ++j)

    {

        // dosometing

    }

}

情况一: 新增物品的质量(v[i]) 大于所需要合成背包的质量 j

这时候背包已经装不下新的物品了,所以可装的物品应该不变,即继承了上一行的值即可    ->    dp[i][j] = dp[i-1][j]

情况二: 新增的物品质量小于等于需要合成背包的质量 j

先给出结论: dp[i][j] =   dp[i-1][j] + dp[i-1][j- p[i] ]

 

我们以第二行为例:

这一行我们新增的物品质量为 5(p[1])

所有 j < 5 的列 (紫色) ,我们都不可以将 5再次加到背包中,于是就继承上一行的值

所有 j >= 5的列 (黄色), 都有可能和之前有过的物品组合成当前的列值, 比如: 坐标为[1][5]这一个格子(亮黄色),我们新增了一个质量为5的物品,之前有一个质量为0的物品,组合起来就可以构成一个质量为5的背包 

p[i]

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0

0

1

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

5

1

1

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

5

2

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

10

3

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

4

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

5

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

上面情况二的例子不够明显,这次我们以坐标为 [5][10]举例;

当前我们所拥有的物品质量: 0,5,5,10,2,3

我们所要合成的背包质量: 10

假设我们已经知道当我们只有 0,5,5,10,2的物品时,有两种合成方法(即当前行的上一行)

当我们新增了一个质量为3的物品时,只要知道了之前有多少种组合可以构成质量为7的背包

现在可构成质量为10 的背包的方法数 = 上一行构成10的值 + 上一行构成7的值

即: 2 + 2 =  4种

p[i]

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0

0

1

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

5

1

1

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

5

2

1

0

0

0

0

2

0

0

0

0

1

0

0

0

0

0

10

3

1

0

0

0

0

2

0

0

0

0

2

0

0

0

0

3

2

4

1

0

1

0

0

2

0

2

0

0

2

0

2

0

0

3

3

5

1

0

1

1

0

3

0

2

2

0

4

0

2

2

0

4

这道题我们所要求解的是和为sum的方法数,即找 [n][sum] 这一点对应的值即可

完整代码


#include 
using namespace std;
#include 
#include 
#include 

int dp[1001][1001];

int digit_sum(int n,int sum,vector& v)
{
    // n 表示给vector中输入了几个数据,但实际上vector里有 n+1 个数据, v[0]是0,非用户输入的
    // sum 表示要求的和为多少
    // v 是一个vector,第一个位置存储了0,后续位置存储了用户输入数据

    // 1. dp第一列全部赋值为1
    for(int i = 0; i <= n; ++i)
    {
        dp[i][0] = 1;
    }
    // 2. dp第一行全部赋值为0
    for(int j = 1; j <= sum; ++j)
    {
        dp[0][j] = 0;
    }
    // 3. 根据上一行的值算出当前位置的值
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= sum; ++j)
        {
            if(v[i] > j)
            {// 如果当前的 v[i]>j, 表示当前的v[i]无法和别的值相加等于j,所以继承上一行的值即可
                dp[i][j] = dp[i-1][j];
            }
            else
            {// 如果当前的 v[i]<=j,就表示v[i]可能和一个比自己小的j值组合为当前的j值
             // 那只需要首先继承上一行的值,再加上对应需要的j值的值即可
                dp[i][j] = dp[i-1][j] + dp[i-1][j-v[i]];
            }
        }
    }
    return dp[n][sum];
}

int main()
{
    int n,sum;
    cin>>n>>sum;
    vector v;
    v.resize(n+1);
    for(int i = 1; i <= n; ++i)
    {
        cin>>v[i];
    }
    int res = digit_sum(n,sum,v);
    cout<

 

你可能感兴趣的:(动态规划 - 数字和为sum的方法数)