问题描述:
给定一个有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(也就是当前列的下标)的背包,黄色格子处填的就是方法数
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的背包,实则还是只有一种方法
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<