题解:洛谷 P1036 选数

【题目来源】

洛谷:P1036 [NOIP 2002 普及组] 选数 - 洛谷(luogu.com.cn)

【题目描述】

已知 \(n\) 个整数 \(x_1,x_2,\dots,x_n\),以及 \(1\) 个整数 \(k(k\lt n)\)。从 \(n\) 个整数中任选 \(k\) 个整数相加,可分别得到一系列的和。例如当 \(n=4\)\(k=3\)\(4\) 个整数分别为 \(3,7,12,19\) 时,可得全部的组合与它们的和为:

\(3+7+12=22\)

\(3+7+19=29\)

\(7+12+19=38\)

\(3+12+19=34\)

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:\(3+7+19=29\)

【输入】

第一行两个空格隔开的整数 \(n,k(1\le n\le 20, k\lt n)\)

第二行 \(n\) 个整数,分别为 \(x_1,x_2,\dots, x_n(1\le x_i\le 5\times 10^6)\)

【输出】

输出一个整数,表示种类数。

【输入样例】

4 3
3 7 12 19

【输出样例】

1

【核心思想】

  1. 问题分析:从 \(n\) 个整数中选出 \(k\) 个,计算所有选法的和,统计其中有多少个是素数。这是一个典型的组合枚举 + 素数判定问题,由于 \(n \le 20\),总组合数 \(C(20,10) \le 184756\),完全可以通过深度优先搜索(DFS)枚举所有组合。

  2. 算法选择

    • DFS 枚举组合:使用递归函数,参数包括当前起始位置 start、还需选 cnt 个数、当前累加和 sum。当 cnt == 0 时,判断 sum 是否为素数,若是则答案加 1。
    • 素数判定:使用试除法,检查到 \(\sqrt{sum}\) 即可。
    • 剪枝:如果剩余数字不足以凑够 cnt 个,直接返回。
  3. 关键步骤

    • 读入:读取 \(n, k\) 和数组 \(a[1..n]\)
    • DFSdfs(start, cnt, sum)
      • cnt == 0if (isPrime(sum)) ans++,返回。
      • n - start + 1 < cnt,返回(剪枝)。
      • 循环 istartn,递归 dfs(i+1, cnt-1, sum+a[i])
    • 输出ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(C(n,k) \times \sqrt{\text{sum}})\)\(n \le 20\),组合数最大约 18 万,每次素数判定约 \(10^3\) 次运算,完全可行。
    • 空间复杂度:\(O(n)\)(递归深度)。
  5. DFS 与组合枚举

    • 组合生成:使用 start 参数确保每次只选后面的元素,避免重复组合。
    • 剪枝优化:提前判断剩余元素是否足够,减少无效递归。
    • 适用场景:这类“从有限集合中选数求和并计数”的问题,常用 DFS 枚举所有组合。

【解题思路】

【算法标签】

普及- #DFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// 定义全局变量
int n, k, a[25], ans = 0;  // n:数字个数, k:选取数量, a:存储数字, ans:结果计数器

// 素数判定函数
bool judge(int num) {
    if (num < 2) return false;  // 小于2的数不是素数
    for (int i = 2; i <= sqrt(num); i++) {  // 只需检查到平方根
        if (num % i == 0) return false;  // 能被整除则不是素数
    }
    return true;  // 否则是素数
}

// 组合枚举函数(递归实现)
void f(int flag, int num, int sum) {
    // flag:当前起始位置, num:剩余需要选择的数字个数, sum:当前累计和

    // 终止条件:已选够k个数字
    if (num == 0) {
        if (judge(sum)) ans++;  // 如果和为素数则计数
        return;
    }

    // 剪枝:剩余数字不足以选够k个
    if (n - flag + 1 < num) return;

    // 递归枚举所有可能的组合
    for (int i = flag; i <= n; i++) {
        f(i + 1, num - 1, sum + a[i]);  // 选择当前数字,递归处理后续
    }
    return;
}

int main() {
    // 输入数据
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];  // 读取数字序列
    }

    // 从第一个数字开始枚举所有k个数字的组合
    f(1, k, 0);

    // 输出满足条件的组合数量
    cout << ans;

    return 0;
}

【运行结果】

4 3
3 7 12 19
1
posted @ 2026-08-31 09:22  团爸讲算法  阅读(11)  评论(0)    收藏  举报