百练 2755 — 神奇的口袋

描述

有一个神奇的口袋,总的容积是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 — 神奇的口袋

发表评论

电子邮件地址不会被公开。 必填项已用*标注

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部