百练 2759 — 神奇的口袋(2)

描述

有一个神奇的口袋,总的容积是400,用这个口袋可以变出一些物品,这些物品的总体积必须是400。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是400,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。

题目链接

http://bailian.openjudge.cn/practice/2759

输入

输入的第一行是正整数n (1 <= n <= 200),表示不同的物品的数目。接下来的n行,每行有一个1到400之间的正整数,分别给出a1,a2……an的值。

输出

输出不同的选择物品的方式的数目对10000取模的结果(因为结果可能很大,为了避免高精度计算,只要求对10000取模的结果)。

样例输入

3
200
200
200

样例输出

3

题目分析

这道题和百练 2755 — 神奇的口袋是相同的,只是数据范围不同,另外本题多了对 10000 取模。
本题中 n 最大为 200,使用深度优先搜索会超时。

动态规划(递归)

设第i个物品的体积为v[i](为啥不用a[i]呢?因为我代码里写的是v,懒得改了)
f(i, j)表示在前i个物品中挑选物品,总体积为j的选择方式的数目。
那么,f(n, 400)就是答案。
如果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][400]就是答案。
如果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]] % 10000 + num[i - 1][j] % 10000) % 10000;
else
    num[i][j] = num[i - 1][j] % 10000;

我们发现,num[i][j]只和num[i-1][j-v[i]]以及num[i-1][j]有关,后面两个在下标上都是小于num[i][j]的。
所以我们可以使用两层循环,从num[1][1]推到num[n][400]
当然,需要先对num数组做一些初始化的工作。具体见代码。

动态规划(递推+优化)

在上面的基础上,num[i, j]在第一维度上只和num[i-1]有关。
所以 num 数组的第一个维度其实可以省略。
另外,在v[i] > j时,num[i][j] = num[i - 1][j],在省略了第一维度后,这个也可以不写了。
num[400]就是最终答案。

AC代码

动态规划(递归)
#include <iostream>
#include <cstring>
using namespace std;
int num[205][405], v[205];

int f(int n, int sum)
{
    if (sum == 0) //sum==0要放在n==0前面,因为num[0][0]的值应该为1
        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]) % 10000 + f(n - 1, sum) % 10000) % 10000;
    else
        num[n][sum] = f(n - 1, sum) % 10000;
    return num[n][sum];
}

int main()
{
    int i, n;
    memset(num, -1, sizeof((num)));
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> v[i];
    cout << f(n, 400) << endl;
    return 0;
}
动态规划(递推)
#include <iostream>
using namespace std;
int num[205][405] = {0}, v[205];
int main()
{
    int i, j, n;
    cin >> n;
    for (i = 0; i < 205; 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 < 401; j++)
        {
            if (v[i] <= j)
                num[i][j] = (num[i - 1][j - v[i]] % 10000 + num[i - 1][j] % 10000) % 10000; //对应上一条注释
            else
                num[i][j] = num[i - 1][j] % 10000;
        }
    }
    cout << num[n][400] << endl;
    return 0;
}
动态规划(递推+优化)
#include <iostream>
using namespace std;
int num[405] = {1}, v[205];
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 = 400; j >= v[i]; j--)
            num[j] = (num[j - v[i]] % 10000 + num[j] % 10000) % 10000;
    cout << num[400] << endl;
    return 0;
}

发表评论

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

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

返回顶部