给定硬币的所有可能总和

Posted

技术标签:

【中文标题】给定硬币的所有可能总和【英文标题】:All possible sum from a given of coins 【发布时间】:2020-06-13 09:39:18 【问题描述】:

你有 n 个具有特定价值的硬币。你的任务是找到你可以使用这些硬币创造的所有货币总和。

输入

第一行有一个整数n:硬币的数量。

下一行有 n 个整数 x1,x2,…,xn:硬币的价值。

输出

首先打印一个整数 k:不同货币总和的数量。在此之后,按升序打印所有可能的总和。

约束

1≤n≤100
1≤xi≤1000

示例

输入:

4
4 2 5 2

输出:

9
2 4 5 6 7 8 9 11 13

我编写了一个代码,它非常适用于小输入,但对大输入给出了错误的答案。请帮助找出错误以及如何纠正它。

我的代码是:

#include <bits/stdc++.h>
using namespace std;

set<long long> s;
// Prints sums of all subsets of array
void subsetSums(long long arr[], long long n)

    // There are totoal 2^n subsets
    long long total = 1 << n;

    // Consider all numbers from 0 to 2^n - 1
    for (long long i = 0; i < total; i++)
    
        long long sum = 0;

        // Consider binary reprsentation of
        // current i to decide which elements
        // to pick.
        for (long long j = 0; j < n; j++)
            if (i & (1 << j))
                sum += arr[j];

        // Print sum of picked elements.
        if (sum)
            s.insert(sum);
    


// Driver code
int main()

    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    long long n;
    cin >> n;
    long long arr[n];
    for (long long i = 0; i < n; i++)
    
        cin >> arr[i];
    
    subsetSums(arr, n);
    cout << s.size() << "\n";
    for (auto it = s.begin(); it != s.end(); ++it)
        cout << *it << " ";
    return 0;

例如,它给出了错误的答案

50
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 

作为

18
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36

正确的输出应该是:

50
1 2 3 4 ... 50

【问题讨论】:

如何改进它?它甚至不适用于 n = 50 您将 long long 存储在一组整数中 请提供一个具体示例,说明您的程序在输入、实际输出和预期输出方面失败。 1&lt;&lt;nint 1 通过n 的左移。它是否适合long long 无关紧要。 【参考方案1】:

您的代码太慢了2^n 子集在最坏的情况下(n=100)给出了‭1,267,650,600,228,229,401,496,703,205,376‬ 子集,而 C++ 平均每秒执行 1000,000,000 操作。

这个问题可以用动态规划来解决,考虑有一个大小为100001的数组dp,这样dp[x]表示x的总和是否可以实现。

基本情况很简单 - 0 的总和无需使用任何硬币即可:dp[0]=1

然后对于每个硬币,我们可以尝试通过硬币价值增加现有总和以填满我们的表格:

for each coinValue:
for coinSum = 100000 - coinValue; coinSum >=0; coinSum--)
    if(dp[coinSum])
    dp[coinSum + coinValue]=1

请注意,我们正在向后循环,这是故意这样做的,以便每个硬币只能使用一次。

复杂度:O(n^2*maxCoinValue)

【讨论】:

【参考方案2】:

你的算法很差,但你得到错误结果的原因是因为你溢出了intlong long total = 1&lt;&lt;n;int 向左移动 n 位置,而您将结果分配给 long long 的事实无关紧要。

您可以使用 ubsan 找到类似的问题。这是您的问题的重现,包括来自 ubsan 的警告消息:

$ clang++ -fsanitize=undefined a.cpp -o a && ./a
50
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
a.cpp:11:25: runtime error: shift exponent 50 is too large for 32-bit type 'int'
a.cpp:22:24: runtime error: shift exponent 32 is too large for 32-bit type 'int'
18
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36

【讨论】:

以上是关于给定硬币的所有可能总和的主要内容,如果未能解决你的问题,请参考以下文章

动态规划算法:硬币的最大总和(小于或等于k)

所有可能的硬币组合

给定一个数组,打印所有可能的连续子序列,其总和可被给定数 x 整除

获取加起来等于给定数字的所有可能总和

如何计算硬币问题的可能组合

如何找到添加两个变量的所有可能组合,每个变量都附加到一个乘数,总和为一个给定的数字(cin)?