T837877 质数和 题目解析

题目

T837877 质数和

题解

题目给定 $N$ 个正整数,要求我们从中恰好选出 $K$ 个数,使得这些数的和为质数,最后统计一共有多少种不同的选法。

这里首先需要明确两个概念:

  1. 必须恰好选择 $K$ 个数,不能多选,也不能少选。
  2. 不同选法按照选取的下标区分,即使两个位置上的数字相同,只要选取的位置不同,就认为是不同的方案。

例如数组为:

$$
[2,2,3]
$$

当 $K=2$ 时,选择第 $1,3$ 个数与选择第 $2,3$ 个数,虽然得到的和都是 $5$,但仍然属于两种不同方案。

因此,这道题实际上由两个子问题组成:

  • 如何枚举恰好选取 $K$ 个数的所有方案?
  • 如何判断一个方案的元素和是否为质数?

我们分别解决。

一、如何枚举全部选法?

观察数据范围:

$$
1\le K\le N\le20
$$

由于 $N$ 的最大值只有 $20$,我们可以考虑直接枚举全部选择方案。

对于每个位置上的数,实际上只有两种选择:

  • 选取这个数。
  • 不选取这个数。

因此,如果不限制必须选取 $K$ 个数,全部可能的选择方案数为:

$$
2^N
$$

当 $N=20$ 时:

$$
2^{20}=1048576
$$

大约有 $100$ 万种方案,这个规模完全可以考虑使用枚举算法。

但是题目只需要恰好选择 $K$ 个数的方案,所以我们可以进一步优化,只枚举符合数量要求的组合。

二、DFS 与组合枚举

这里介绍使用 DFS(深度优先搜索)完成组合枚举的方法。

假设给定数组:

$$
[1,2,3,4]
$$

要求选出恰好 $2$ 个元素,那么所有按照下标区分的选择方案为:

$$
(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)
$$

我们可以观察到一个规律:如果每次都规定新选择的下标必须大于上一次选择的下标,那么就能保证同一组下标不会被重复枚举。

例如选择下标 $1,3$ 之后,我们就不会再枚举 $3,1$,因为二者实际上是同一种选法。

我们不妨定义一个 DFS 函数:

dfs(start, cnt, sum);

其中:

  • start:下一次可以选择的最小下标。
  • cnt:当前已经选取的元素数量。
  • sum:当前选取的所有元素之和。

初始状态显然为:

dfs(1, 0, 0);

假设当前选择了下标为 $i$ 的元素,那么下一次只能从 $i+1$ 开始选择,因此递归调用:

dfs(i + 1, cnt + 1, sum + a[i]);

当 cnt == K 时,说明已经完成了一种合法数量的选择方案,此时不需要再选择其他元素,直接判断 sum 是否为质数即可。

如果是质数,答案增加 $1$;否则直接结束当前递归分支。

还有一个优化:假设当前已经选择了 cnt 个数,还需要选择 $K-cnt$ 个,而当前剩余可供选择的元素数量不足 $K-cnt$,那么无论怎样继续搜索,都不可能形成合法方案,可以提前结束。

实际上,我们也可以直接限制枚举下标的上界,避免进入这些无意义的分支。

三、如何判断质数?

根据题目定义,质数是大于 $1$,并且只有 $1$ 和自身两个正因数的整数。

例如 $2,3,5,7,11$ 都是质数,而 $1,4,6,8,9$ 都不是质数。

最容易想到的方法是试除法:对于一个整数 $x$,尝试判断是否存在满足以下条件的整数 $i$:

$$
2\le i<x,\qquad x\bmod i=0
$$

如果存在这样的整数,那么 $x$ 就不是质数。

但是,我们并不需要枚举到 $x-1$。

假设 $x$ 是合数,那么必然能够表示为:

$$
x=a\times b
$$

其中 $a,b$ 都是大于 $1$ 的整数。

不妨令 $a\le b$,那么显然:

$$
a^2\le ab=x
$$

因此:

$$
a\le\sqrt{x}
$$

也就是说,如果 $x$ 是合数,那么它一定存在一个不超过 $\sqrt{x}$ 的因数。

所以判断质数只需要枚举:

$$
2\le i\le\sqrt{x}
$$

如果没有找到任何因数,并且 $x>1$,那么 $x$ 就是质数。

对应代码:

bool is_prime(int x)
{
    if (x < 2) return false;

    for (int i = 2; i * i <= x; ++i)
    {
        if (x % i == 0) return false;
    }

    return true;
}

这里特别注意,$1$ 不是质数,必须单独判断。

另外,循环条件 i * i <= x 与 i <= sqrt(x) 的含义相同,但是前者不需要使用浮点数运算,更适合本题。

四、时间复杂度分析

我们先计算需要枚举多少种选择方案。

从 $N$ 个位置中选择恰好 $K$ 个位置,组合数为:

$$
\binom{N}{K}=\frac{N!}{K!(N-K)!}
$$

当 $N=20$ 时,组合数的最大值出现在 $K=10$:

$$
\binom{20}{10}=184756
$$

所以我们最多只需要检查不到 $19$ 万种完整的选择方案。

再考虑质数判断。

题目保证:

$$
a_i\le1000
$$

且最多选择 $20$ 个数,因此任意一种方案的元素和都不超过:

$$
20\times1000=20000
$$

每次质数判断最多枚举大约 $\sqrt{20000}$ 个候选因数,计算量完全可以接受。

因此,不需要使用特别复杂的算法,仅通过 DFS 组合枚举与试除法,就足以解决这道题。

五、C++ 代码

#include <bits/stdc++.h>

using i128 = __int128;
using ll = long long;
using ull = unsigned long long;
using de = double;
using ld = long double;
using namespace std;

bool is_prime(int x)
{
    if (x < 2) return false;

    for (int i = 2; i * i <= x; ++i)
    {
        if (x % i == 0) return false;
    }

    return true;
}

void solve()
{
    int n, k; cin >> n >> k;

    vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i) cin >> a[i];

    ll ans = 0;

    auto dfs = [&](auto&& dfs, int start, int cnt, int sum) -> void
    {
        if (cnt == k)
        {
            if (is_prime(sum)) ans++;
            return;
        }

        // 剩余位置必须足够选满 k 个元素
        for (int i = start; i <= n - (k - cnt) + 1; ++i)
        {
            dfs(dfs, i + 1, cnt + 1, sum + a[i]);
        }
    };

    dfs(dfs, 1, 0, 0);

    cout << ans << '\n';
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int t = 1;
    // cin >> t;

    while (t--)
    {
        solve();
    }
}

六、正确性分析

为了确认 DFS 没有重复枚举或者遗漏合法方案,我们可以从两个方面进行分析。

首先,所有合法方案一定会被枚举到。

对于任意一种合法选法,假设选中的 $K$ 个下标从小到大为:

$$
i_1<i_2<\cdots<i_K
$$

由于 DFS 每次都可以在当前下标之后继续选择,因此一定存在一条搜索路径,依次选择 $i_1,i_2,\ldots,i_K$。

所以全部合法方案都可以被搜索到。

其次,每种合法方案只会被枚举一次。

因为 DFS 规定每次新选择的下标必须严格大于前一个下标,所以同一组位置只能以升序被选择。

因此,不可能出现同一组下标以不同顺序被重复枚举的情况。

当选中元素数量达到 $K$ 时,我们恰好统计一次该方案,并使用正确的质数判断函数检查其元素和。

综上,算法能够恰好统计所有满足条件的选择方案。

七、总结

本题主要考查组合枚举与质数判断。

对于 $N\le20$ 这样的数据范围,我们应当主动考虑枚举或搜索,而不是一看到选取若干元素,就立即想到动态规划。

此外,本题与全排列枚举有一个本质区别:组合不关心选取顺序,因此我们可以要求下标严格递增,从而避免重复枚举。

关于复杂度,令 $A=\max(a_i)$,DFS 的搜索与质数判断总时间复杂度可以写为:

$$
O\left(K\binom{N}{K}+\binom{N}{K}\sqrt{KA}\right)
$$

由于本题 $N\le20$,该复杂度能够满足题目要求。

递归深度最多为 $K$,因此辅助空间复杂度为 $O(N)$。

难度: 普及−(橙,偏上,个人评估)

考点: 组合枚举、DFS、回溯、质数判断、剪枝

时间复杂度: $O\left(K\binom{N}{K}+\binom{N}{K}\sqrt{KA}\right)$

空间复杂度: $O(N)$

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇