题目的主要信息:

  • 现有一组砝码,重量互不相等,分别为m1m_1m1,m2m_2m2,m3m_3m3mnm_nmn
  • 每种砝码对应的数量为x1x_1x1,x2x_2x2,x3x_3x3 ...xnx_nxn
  • 现在要用这些砝码去称物体的重量(放在同一侧),问能称出多少种不同的重量
  • 称重重量包括0

方法一:集合

具体做法:

我们可以先从砝码比较少的情况开始想:只有2两个1g砝码和1个2g砝码能组成多少种?

  • 首先0肯定是一种;
  • 使用1个1g加上现有的0g构成一种;
  • 使用1个1g加上现有的0g重复了,使用1个1g加上现有的1g构成2g;
  • 使用1个2g加上现有的0g重复了,使用1个2g加上现有1g构成3g,使用1个2g加上现有的2g构成4g;

alt 如上所示,一共就有5种情况。在砝码种类和数量都比较多的时候,我们也可以从0g开始,遍历每一种的重量的砝码的每一个(即遍历每个砝码),对当前集合种所有的种类都加上这个重量,如果重复则去除,否则加入。

去重操作我们采用unordered_set,无序集合, 每次加构成的新重量加入集合,集合会自动去重,最后集合的大小就是可以称的不同重量。

#include<iostream>
#include<vector>
#include<unordered_set>
#include<algorithm>
using namespace std;

int main(){
    int n;
    while(cin >> n){
        vector<int> weight(n);
        vector<int> num(n);
        for(int i = 0; i < n; i++) //输入n种砝码重量
            cin >> weight[i];
        for(int i = 0; i < n; i++) //输入n种砝码的数量
            cin >> num[i];
        unordered_set<int> s; //集合用于去重
        s.insert(0); //0也是一种
        for(int i = 0; i < n; i++){ //对于每一种砝码
            for(int j = 1; j <= num[i]; j++){ //用完之前数量之前
                unordered_set<int> temp(s);
                for(auto iter = temp.begin(); iter != temp.end(); iter++) //当前集合每个都可以加一次
                    s.insert(*iter + weight[i]);
            }
        }
        cout << s.size() << endl; //最后集合的大小就是种类
    }
    return 0;
}

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),其中nnn为所有的砝码的全部重量相加,因为砝码种类不超过10,每种数量都是[1,6][1,6][1,6],因此遍历每个砝码其实是常数时间,最多不超过60次,每次都要遍历集合,集合中是构成的所有重量种类,不超过O(n)O(n)O(n),而因为无序,每次集合操作都是O(1)O(1)O(1)
  • 空间复杂度:O(n)O(n)O(n),集合的大小最大为n+1n+1n+1

方法二:动态规划

具体做法:

我们可以先计算出所有砝码总重量,然后用长为总重量sum+1sum+1sum+1的bool型的dp数组计算每种重量是否可能出现,最后遍历这个dp数组统计每种重量出现的次数。

首先dp[0]=1dp[0]=1dp[0]=1,然后我们遍历每个砝码(即每种重量的每一个),每次从sum开始往下递减,如果该重量k减去当前遍历到的这个砝码的重量得到的那个重量已经出现了即已经被别的组装好了,那我们就认为这个重量可以出现,置为1.

这个逻辑和背包问题很像:dp[k]=dp[kweight[i]]dp[k] = dp[k - weight[i]]dp[k]=dp[kweight[i]]

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main(){
    int n;
    while(cin >> n){
        vector<int> weight(n);
        vector<int> num(n);
        int sum = 0;
        for(int i = 0; i < n; i++) //输入n种砝码重量
            cin >> weight[i];
        for(int i = 0; i < n; i++){//输入n种砝码的数量
            cin >> num[i];
            sum += num[i] * weight[i]; //砝码总重量
        }
        vector<bool> dp(sum + 1, false); //记录0到sum是否可行
        dp[0] = true;
        for(int i = 0; i < n; i++){ //遍历每一种砝码
            for(int j = 0; j < num[i]; j++){ //遍历砝码每一个数量
                for(int k = sum; k >= weight[i]; k--) //每次剩余的每个都可以添加
                    if(dp[k - weight[i]])
                        dp[k] = 1;
            }
        }
        int count = 0;
        for(int i = 0; i <= sum; i++) //找到为1的就是可以组成的质量,计数
            if(dp[i])
                count++;
        cout << count << endl;
    }
    return 0;
}

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),其中nnn为所有的砝码的全部重量相加,因为砝码种类不超过10,每种数量都是[1,6][1,6][1,6],因此遍历每个砝码其实是常数时间,最多不超过60次,每次都要遍历dp数组,不超过O(n)O(n)O(n)
  • 空间复杂度:O(n)O(n)O(n),dp数组的大小为n+1n+1n+1