P8646 [蓝桥杯 2017 省 AB] 包子凑数
题目描述
小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有 种蒸笼,其中第 种蒸笼恰好能放 个包子。每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买 个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有 个包子。比如一共有 种蒸笼,分别能放 、 和 个包子。当顾客想买 个包子时,大叔就会选 笼 个的再加 笼 个的(也可能选出 笼 个的再加 笼 个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有 种蒸笼,分别能放 、 和 个包子。而顾客想买 个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入格式
第一行包含一个整数 。。
以下 行每行包含一个整数 。。
输出格式
一个整数代表答案。如果凑不出的数目有无限多个,输出 INF。
输入输出样例 #1
输入 #1
2
4
5
输出 #1
6
输入输出样例 #2
输入 #2
2
4
6
输出 #2
INF
说明/提示
对于样例 ,凑不出的数目包括:。
对于样例 ,所有奇数都凑不出来,所以有无限多个。
蓝桥杯 2017 省赛 A 组 H 题。
解析
核心问题一:什么时候会输出 INF(有无限个数凑不出)?
【思路解析】
题目问:给你 种包子(比如一笼 4 个、一笼 6 个),你能不能凑出所有的数字?如果有无数个数字凑不出,就输出 INF。
这里直接用到数学上的一个著名定理:裴蜀定理。 用“人话”来说就是:如果这些数字的最大公约数(GCD)大于 1,那么它们能凑出来的数,永远只能是这个公约数的倍数!
-
举个通俗的例子: 假设你有两种蒸笼:一笼 4 个,一笼 6 个。 它们的最大公约数是 2。 你能凑出多少个包子?4, 6, 8, 10, 12, 14… 全都是偶数! 那么奇数呢(1, 3, 5, 7, 9, 11…)?永远凑不出来! 因为奇数有无限多个,所以此时凑不出的数有无限个,直接输出
INF。 -
反之: 如果一笼是 4 个,一笼是 5 个。 它们的最大公约数是 1(我们称之为“互质”)。 只要公约数是 1,数学上可以证明:凑不出的数字数量一定是有限的!(数字大到一定程度后,后面的所有数字都能凑出来)。
【代码对应】
这就是为什么我们在代码一开始,要用一个循环把所有数字的 gcd(最大公约数)求出来:
int g = a[0];
for(int i = 1; i < n; i++) {
g = std::gcd(g, a[i]);
}
if(g > 1) { cout << "INF\n"; return; }核心问题二:如果凑不出的数是“有限的”,怎么找?为什么要开 100000 的数组?
【思路解析】 好,现在我们知道最大公约数是 1 了,凑不出的数是有限的。但问题是:这个“有限”到底有多大?我们需要算到多大的数字才算到头?
这里又要用到一个数学定理:赛瓦维斯特定理(麦乐鸡块定理 / 凑硬币定理)。 定理内容:如果两个数 和 互质,那么它们最大凑不出来的数字是 。
- 比如 4 和 5:最大凑不出的数字是 。也就是说,12 及以上的数字绝对都能凑出来!
回到本题的数据范围: 题目说,每笼包子最多 个(即 )。 哪怕情况最极端,只有两个互质的数,比如 99 和 100,它们凑不出的最大数字也就是 。 也就是说,无论题目怎么给数据,超过 10000 左右的数字是绝对能凑出来的!
【代码对应】
为了绝对安全,我们把动态规划的数组上限 MAX_DP 设为 100000(10万)。10万以后的数不用管了,肯定都能凑出来。
核心问题三:怎么求具体有几个数凑不出?(完全背包问题)
【思路解析】 现在我们圈定了一个范围(1 到 100000),我们需要在这个范围内挨个判断:数字 能不能凑出来?
这就变成了一个标准的**“完全背包问题”。因为每种蒸笼(每种物品)你可以买无限次**。
- 状态定义:
我们用一个数组
dp[j]表示数字 能不能凑出来。0表示凑不出,1表示能凑出。 - 初始状态(极其重要):
dp[0] = 1;凑出 0 个包子是可能的(就是什么都不买)。这是一个“种子”,所有的数字都是从 0 开始加上去的。如果不把 0 设为 1,后面的数永远推导不出来。 - 状态转移过程:
假设我们现在手里有一种包子,一笼是 5 个(
a[i] = 5)。 我们从小到大看每一个数字 。如果我问你:12 个包子能凑出来吗? 你只需要往前看 5 步:7 个包子能凑出来吗?- 如果 7 个包子能凑出来(
dp[7] == 1),那我再加一笼 5 个的,绝对就能凑出 12 个!所以dp[12] = 1。 - 如果 7 个包子凑不出,那 12 目前也凑不出。
- 如果 7 个包子能凑出来(
【代码对应】 这就是这两层循环的核心逻辑:
dp[0] = 1; // 种子:0个包子是可以凑出的
for (int i = 0; i < n; i++) // 遍历每一笼包子 a[i]
{
for (int j = a[i]; j < MAX_DP; j++) // 从能买一笼 a[i] 的数字开始往后推
{
if (dp[j - a[i]] == 1) // 如果差一笼包子的那个数字能凑出来
{
dp[j] = 1; // 那么加上这笼包子,当前的数字 j 也能凑出来!
}
}
}代码
//
// Created by 23264 on 2026/2/22.
//
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
int n;
vector<int> a;
vector<int> dp;
constexpr int MAX_DP = 100005;
void solve()
{
int g = a[0];
for (int i = 1;i < n;i++)
{
g = gcd(g,a[i]);
}
if (g > 1)
{
cout << "INF" << "\n";
return;
}
dp[0] = 1;
for (int i = 0;i < n;i++)
{
for (int j = a[i];j < MAX_DP;j++)
{
if (dp[j - a[i]] == 1)
{
dp[j] = 1;
}
}
}
int ans = 0;
for (int i = 1;i < MAX_DP;i++)
{
if (dp[i] == 0)
{
ans++;
}
}
cout << ans << "\n";
}
int main()
{
IOS;
cin >> n;
a.resize(n);
dp.resize(MAX_DP,0);
for (int i = 0;i < n;i++)
{
cin >> a[i];
}
solve();
}