题解: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\))。

总结

这道题目需要通过缜密的思考过后得出整体思路,类似题型也需要通过缜密的思考后方可得出答案,一定不要漏掉了那些看似微小的细节!

posted @ 2025-11-15 11:19  linruicong  阅读(7)  评论(0)    收藏  举报