【洛谷】P8646 [蓝桥杯 2017 省 AB] 包子凑数 的题解
题目传送门
思路
题上要问的是凑不出的数目,如果直接推断哪个数不可能凑出是非常困难的。
所以我们考虑反向解决这个问题:
在合适的范围内将所有可以凑出的数枚举出来,然后用总数减去凑出的数的数目就是答案了。
至于合适的范围这件事情,可以想的:
两个数 n n n 和 m m m 最大的不可能凑出的数是 n × m − n − m n \times m-n-m n×m−n−m,题上给出的 a i a_i ai 最大是 100 100 100,抛去 n n n 和 m m m 不可能同时为 100 100 100 这件事情,最大的不可能凑出的数也不会超过 100 × 100 − 200 100 \times 100-200 100×100−200。
在枚举之前还有一个问题,INF
。
什么情况下会有无数个数无法凑出呢?
样例二的输入其实已经给出了答案,如果所有的 a i a_i ai 不互质,也就是说它们都是某一个大于 1 1 1 的数的倍数,就会有无数个的数无法凑出,毕竟这样的 a i a_i ai 所能构造出的数都是它们 gcd \gcd gcd 的倍数。
我们用一个 v i s vis vis 数组来标记每一个数能否被凑出,首先 0 0 0 肯定是可以的,也就是不买。
接下来我们用一个类似动规的思想去思考枚举问题:
如果当前的某个数 j j j 可以被凑出,那么由这个数与每个 a i a_i ai 相加的结果 j + a i j + a_i j+ai 也可以被凑出,所以就有了公式:
if(vis[j]) {
vis[j + a[i]] = true;
}
有了这个公式以后,思路也就明朗了,对 j j j 做 0 0 0 到 m a x n maxn maxn 的循环, 对 i i i 做 1 1 1 到 n n n 的循环就好了。
代码
#include <bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
int n, a[105];
bool vis[10005];
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int main() {
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> a[1];
int gcdn = a[1];
for(int i = 2; i <= n; i ++) {
cin >> a[i];
gcdn = gcd(gcdn, a[i]);
}
if(gcdn != 1) {
cout << "INF";
return 0;
}
vis[0] = true;
for(int i = 1; i <= n; i ++) {
for(int j = 0; j + a[i] <= 10000; j ++) {
if(vis[j]) vis[j + a[i]] = true;
}
}
int res = 0;
for(int i = 1; i <= 10000; i ++) {
if(!vis[i]) res ++;
}
cout << res;
return 0;
}