题目
包子凑数https://www.lanqiao.cn/problems/98/learning/?page=1&first_category_id=1&tag_category_id=59
题目描述
小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有 N 种蒸笼,其中第 𝑖 种蒸笼恰好能放 𝐴𝑖个包子。每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买 𝑋 个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有 𝑋 个包子。比如一共有 3 种蒸笼,分别能放 3、4 和 5 个包子。当顾客想买 11 个包子时,大叔就会选 2 笼 3 个的再加 1 笼 5 个的(也可能选出 1 笼 3 个的再加 2 笼 4 个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有 3 种蒸笼,分别能放 4、5 和 6 个包子。而顾客想买 7 个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入描述
第一行包含一个整数 N (1≤𝑁≤100)。
以下 N 行每行包含一个整数 𝐴𝑖(1≤𝐴𝑖≤100)。
输出描述
一个整数代表答案。如果凑不出的数目有无限多个,输出 INF。
输入输出样例
示例 1
输入
2
4
5
输出
6
样例说明
凑不出的数目包括:1, 2, 3, 6, 7, 11。
示例 2
输入
2
4
6
输出
INF
样例说明
所有奇数都凑不出来,所以有无限多个
运行限制
最大运行时间:1s
最大运行内存: 256M
我的想法
我看了
题解:
“
解题思路
这个是从一个简单dp问题演化而来的: 手里分别有无数张面额为x,y,z元的纸币,请问凑出i元的方法有多少种:
动态规划表达:dp[i],dp[i]表示凑出i元有多少种可能-----------------------------------------------------------------------------------------实现: dp[i] = dp[i]+dp[i-x]+dp[i-y]+dp[i-z]; ::意为, 当前如果要凑i元钱,凑出之前必然是 i-x,i-y,i-z元中的其中一个,如果i-x,i-y,i-z中的任意一个能凑出来,dp[i]就可以凑出来
”
学会了这道题的解法:
包子凑数这道题关心的是哪些金额无法凑出。所以不用完全计算凑出的方法有多少种。
一个更直接的方法是使用一个布尔数组canReach[i]
来表示金额i
是否可以凑出
C++中布尔类型可以隐式转换为整数(true
为1,false
为0),所以我可以使用一个整数数组dp
,其中dp[i]
表示金额i
是否可以凑出(如果dp[i]
为正数,则表示可以;如果为0,则表示不可以)
其中,maxAmount的值为10000,因为题目限制最多100种蒸笼,每个蒸笼最多100个包子。
而无法凑出的数超过了一半的maxAmount,我
就认为它们是“无限多”的。
初始化canReach
数组,其中canReach[0] = true
,其余元素为false
。
假设capacities
是一个包含不同蒸笼(或容器)容量的数组,例如[1, 2, 5]
。
一旦为当前包子数i
找到了一个可行的组合(即canReach[i] = true
),就通过break
语句跳出内层循环,因为没有必要再检查其他容量了。这个优化减少了不必要的计算。
举例解释动态规划过程
假设capacities = [1, 2, 5]且maxAmount = 10。
初始状态:
canReach = [true, false, false, false, false, false, false, false, false, false, false]
处理包子数1:
检查1,发现canReach[1-1] = true,因此canReach[1] = true。
更新后:[true, true, false, false, false, false, false, false, false, false, false]
处理包子数2:
检查1,canReach[2-1] = true,但已处理过。
检查2,canReach[2-2] = true,因此canReach[2] = true。
更新后:[true, true, true, false, false, false, false, false, false, false, false]
继续处理直到maxAmount:
对于每个包子数,重复上述过程,直到maxAmount。
最终结果:
canReach数组将展示哪些包子数可以被凑出。
代码
#include <iostream>
#include <vector>
#include <algorithm> // 用于std::max
using namespace std;
int main() {
int N;
cin >> N; // 蒸笼种类数
vector<int> capacities(N);
for (int i = 0; i < N; ++i) {
cin >> capacities[i]; // 读取每种蒸笼的容量
}
// 假设最大金额不超过10000(这个值可以根据实际情况调整)
int maxAmount = 10000;
vector<bool> canReach(maxAmount + 1, false);
canReach[0] = true; // 0元总是可以凑出的
// 动态规划过程
for (int i = 1; i <= maxAmount; ++i) {
for (int cap : capacities) {
if (i >= cap && canReach[i - cap]) {
canReach[i] = true;
break; // 一旦发现一种方式可以凑出i,就无需再检查其他蒸笼
}
}
}
// 计算无法凑出的金额数量
int count = 0;
for (int i = 1; i <= maxAmount; ++i) {
if (!canReach[i]) {
++count;
}
}
// 如果存在一种容量是其他所有容量的公因数(除了1),则可能无法凑出的数是无限的
// 但为了简化问题,我们只计算在给定的maxAmount内无法凑出的数的数量
// 如果需要判断“无限多”,则需要更复杂的分析(通常涉及数学上的证明)
if (count >= maxAmount / 2) { // 一个粗略的估计,用于判断是否“无限多”
cout << "INF" << endl;
} else {
cout << count << endl;
}
return 0;
}