题解:洛谷 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
【核心思想】
-
问题分析:从 \(n\) 个整数中选出 \(k\) 个,计算所有选法的和,统计其中有多少个是素数。这是一个典型的组合枚举 + 素数判定问题,由于 \(n \le 20\),总组合数 \(C(20,10) \le 184756\),完全可以通过深度优先搜索(DFS)枚举所有组合。
-
算法选择:
- DFS 枚举组合:使用递归函数,参数包括当前起始位置
start、还需选cnt个数、当前累加和sum。当cnt == 0时,判断sum是否为素数,若是则答案加 1。 - 素数判定:使用试除法,检查到 \(\sqrt{sum}\) 即可。
- 剪枝:如果剩余数字不足以凑够
cnt个,直接返回。
- DFS 枚举组合:使用递归函数,参数包括当前起始位置
-
关键步骤:
- 读入:读取 \(n, k\) 和数组 \(a[1..n]\)。
- DFS:
dfs(start, cnt, sum):- 若
cnt == 0,if (isPrime(sum)) ans++,返回。 - 若
n - start + 1 < cnt,返回(剪枝)。 - 循环
i从start到n,递归dfs(i+1, cnt-1, sum+a[i])。
- 若
- 输出:
ans。
-
时间/空间复杂度:
- 时间复杂度:\(O(C(n,k) \times \sqrt{\text{sum}})\),\(n \le 20\),组合数最大约 18 万,每次素数判定约 \(10^3\) 次运算,完全可行。
- 空间复杂度:\(O(n)\)(递归深度)。
-
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
浙公网安备 33010602011771号