描述
有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。
题目链接
http://bailian.openjudge.cn/practice/2755
输入
输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。
输出
输出不同的选择物品的方式的数目。
样例输入
3
20
20
20
样例输出
3
题目分析
深度优先搜索
n最大为20,所以将每一种情况都枚举出来即可。深搜其实就是一种枚举。
如果n比较大,这种方法就可能会超时。
动态规划(递归)
设第i个物品的体积为v[i](为啥不用a[i]呢?因为我代码里写的是v,懒得改了)
设f(i, j)
表示在前i个物品中挑选物品,总体积为j的选择方式的数目。
那么,f(n, 40)
就是答案。
如果v[i] <= j
,那么对于第i个物品,可以选择放入口袋或不放入口袋。如果把它放入口袋,那么选择方式有f(i, j-v[i])
种;如果不把它放入口袋,那么选择方式有f(i-1, j)
种。
这时,f(i, j) = f(i-1, j-v[i]) + f(i-1, j)
如果v[i] > j
,那么对于第i个物品,只能选择不放入口袋。
这时,f(i, j) = f(i-1, j)
现在就可以使用递归的方法写出函数 f(int i, int j)
了。
但是,很容易会发现,这里面存在很严重的重复计算的问题。
所以,我们添加一个数组,设num[i][j]
表示f(i, j)
的值,将计算出的f(i, j)
保存下来即可。下次再需要使用f(i, j)
的时候,直接使用num[i][j]
即可。
这叫做记忆化搜索。
当然了,递归函数是要有出口的,具体的我就不分析了,大家可以参考我的代码。
动态规划(递推)
和上面的推理类似。
设num[i][j]
表示在前i个物品中挑选物品,总体积为j的选择方式的数目。
那么,num[n][40]
就是答案。
如果v[i] <= j
,那么对于第i个物品,可以选择放入口袋或不放入口袋。如果把它放入口袋,那么选择方式有num[i][j-v[i]]
种;如果不把它放入口袋,那么选择方式有num[i-1][j]
种。
这时,num[i][j] = num[i-1][j-v[i]] + num[i-1][j]
如果v[i] > j
,那么对于第i个物品,只能选择不放入口袋。
这时,num[i][j] = num[i-1][j]
if (v[i] <= j)
num[i][j] = num[i - 1][j - v[i]] + num[i - 1][j];
else
num[i][j] = num[i - 1][j];
我们发现,num[i][j]
只和num[i-1][j-v[i]]
以及num[i-1][j]
有关,后面两个在下标上都是小于num[i][j]
的。
所以我们可以使用两层循环,从num[1][1]
推到num[n][40]
。
当然,需要先对num数组做一些初始化的工作。具体见代码。
动态规划(递推+优化)
在上面的基础上,num[i, j]
在第一维度上只和num[i-1]
有关。
所以 num 数组的第一个维度其实可以省略。
另外,在v[i] > j
时,num[i][j] = num[i - 1][j]
,在省略了第一维度后,这个也可以不写了。
num[40]
就是最终答案。
AC代码
深度优先搜索
#include <iostream>
using namespace std;
int n, v[25], sum = 0, res = 0;
void dfs(int m)
{
if (sum == 40)
res++;
if (sum >= 40)
return;
if (m == n)
return;
//放当前这个物品
sum += v[m];
dfs(m + 1);
//不放当前这个物品
sum -= v[m];
dfs(m + 1);
}
int main()
{
int i;
cin >> n;
for (i = 0; i < n; i++)
cin >> v[i];
dfs(0);
cout << res << endl;
return 0;
}
动态规划(递归)
#include <iostream>
#include <cstring>
using namespace std;
int num[25][45], v[25];
int f(int n, int sum);
int main()
{
memset(num, -1, sizeof(num));
int i, j, n;
cin >> n;
for (i = 1; i <= n; i++)
cin >> v[i];
cout << f(n, 40) << endl;
return 0;
}
int f(int n, int sum)
{
if (sum == 0)
return 1;
if (n == 0)
return 0;
if (num[n][sum] != -1)
return num[n][sum];
if (v[n] <= sum)
num[n][sum] = f(n - 1, sum - v[n]) + f(n - 1, sum);
else
num[n][sum] = f(n - 1, sum);
return num[n][sum];
}
动态规划(递推)
#include <iostream>
using namespace std;
int num[25][45] = {0}, v[25];
int main()
{
int i, j, n;
cin >> n;
for (i = 0; i < 25; i++)
num[i][0] = 1; //这一点非常重要,对应下一条注释,当j==v[i]时,算一种
for (i = 1; i <= n; i++)
cin >> v[i];
for (i = 1; i <= n; i++)
{
for (j = 1; j < 41; j++)
{
if (v[i] <= j)
num[i][j] = num[i - 1][j - v[i]] + num[i - 1][j]; //对应上一条注释
else
num[i][j] = num[i - 1][j];
}
}
cout << num[n][40] << endl;
return 0;
}
动态规划(递推+优化)
#include <iostream>
using namespace std;
int num[45] = {1}, v[25];
int main()
{
int i, j, n;
cin >> n;
for (i = 1; i <= n; i++)
cin >> v[i];
for (i = 1; i <= n; i++)
for (j = 41; j >= v[i]; j--)
num[j] = num[j - v[i]] + num[j];
cout << num[40] << endl;
return 0;
}
一个回复在 “百练 2755 — 神奇的口袋”