题解:B4290 [蓝桥杯青少年组省赛 2022] 组合
形式化的题面
有一个长度为 \(n\) 的数列 \(a\)(\(1 \le n \le 20\)),求有多少个数不可以被序列 \(a\) 中的数以加和的形式组合出来。如果有无限个,就输出 \(-1\)。
题目分析
看懂了题目之后,其实可以发现题目就只有这两种答案情况:
- 无限情况。
- 有限,输出个数。
我们分别考虑不同情况该如何处理。
无解情况
我们可以先求出这几个数的最大公约数。假如最大公约数不为 \(1\),那么就无解。
为什么这样做是正确的?
我们假设 \(n\) 个数的最大公约数是 \(gcdd\)。假如 \(gcdd\) 不为 \(1\),那么这 \(n\) 个数必定只可以组合出是 \(gcdd\) 倍数的数。
有解情况
考虑递推。
我们使用 \(f_i\) 表示数 \(i\) 是否可以被组合出来。枚举指针 \(j\),表示枚举 \(a\) 数列的指针。
若 \(i-a_j\) 可以被表示出来。则 \(i\) 必定可以通过加上 \(a_j\) 的方式被表示出来。状态转移方程如下:
\[f_i=f_i \lor f_{i-a_j}
\]
代码实现
首先,直接通过枚举的方式求最大公约数必定会超时。这里给出一种不会超时的做法。
欧几里得算法(辗转相除法)
我们定义 \(\gcd (x,y)\) 为 \(x\) 和 \(y\) 的最大公约数。那么实现如下:
- 若 \(y\) 为 \(0\),则 \(\gcd (x,y)=x\)。
- 否则,\(\gcd (x,y)= \gcd (y,x\bmod y)\)。
给出代码实现:
int gcd(int x,int y)
{
if(y==0) return x;
return gcd(y,x%y);
}
完整 AC 代码实现
#include<bits/stdc++.h>
using namespace std;
int n,a[30],gcdd,cnt,ans;
bool f[2000005];
int gcd(int x,int y)//辗转相除法求最大公约数
{
if(y==0) return x;
return gcd(y,x%y);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);//输入
if(i==1)
{
gcdd=a[i];
continue;
}
gcdd=gcd(gcdd,a[i]);//预处理最大公约数
}
sort(a+1,a+1+n);
f[0]=1;
if(gcdd!=1)//若最大公约数不为1,则输出-1,证明见上
{
printf("-1");
return 0;
}
for(int i=1;;i++)
{
for(int j=1;j<=n;j++)
{
if(i<a[j]) continue;//特判,防止越界
if(f[i-a[j]])
{
f[i]=1;
break;
}
}
if(!f[i])//若无法表示
{
ans++;//答案+1
cnt=0;//连续可表达个数清零
}
else cnt++;
if(cnt==a[1]) break;//若连续可表达个数=a[1],退出循环
}
printf("%d",ans);
return 0;
}
终止条件
if(cnt==a[1]) break; 此处,为什么要当 \(cnt=a_1\) 时退出循环呢?
如果可以被表示出来的数构成一个长度为 \(a_1\) 的序列时,接下来更大的数也可以被表示出来了。因为接下来的数可以被这些序列中的数加上 \(a_1\) 表示出来。例如:
- 连续序列 \(2,3,4,5,6\) 可以被表示出来(长度为 5)。
- 那么接下来的序列 \(7,8,9,10,11\) 同样可以通过原数列中对应位置的数加上长度表示出来。
- 而又因为长度为数列 \(a\) 中的一个数,所以接下来的数都可以以此类推,表示出来。
由于我们要使得循环次数最少。因此,长度只要到 \(a\) 序列中的最小数即可(排序过后是 \(a_1\))。
总结
这道题目需要通过缜密的思考过后得出整体思路,类似题型也需要通过缜密的思考后方可得出答案,一定不要漏掉了那些看似微小的细节!

浙公网安备 33010602011771号