题目描述
已知 n 个整数 1,2,⋯ ,x1,x2,⋯,xn,以及 1个整数 k(1<k<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
3+7+12=22
3+7+19=2
7+12+19=38
3+12+19=34
现在,要求你计算出和为素数共有多少种。
例如上例,只有一种的和为素数:3+7+19=29。
输入格式
第一行两个空格隔开的整数 n,k(1≤n≤20,k<n)。
第二行 n 个整数,分别为 1,2,⋯ ,x1,x2,⋯,xn。
输出格式
输出一个整数,表示种类数。
解题思路
读题之后其实我们可以想到,这其实是一个组合型枚举问题,即从n个数中取k个数,然后从其中所有结果中去判断是否满足题意,这样的话我们可以使用dfs来解决此类问题,上文说到这是一个组合型枚举问题,如果对组合型枚举不是很熟练的话这边有一个很好的练习题:https://oj.haizeix.com/problem/236
就是递归实现组合型枚举,事实上我们这个题的问题搜索树就是每一次取k个数用结果去验证是否为素数。难点就是如何去枚举到不同的k个数。
其实这个很简单,k个数就是k层循环嘛,O(∩_∩)O,比如2个数就是双重循环,3个数就是3重循环,但是k个数,k在我们解题之前并不是一个常量,我们设计循环就很难设计┭┮﹏┭┮,既然这样,枚举能解决的问题递归肯定也可以,但是在递归中我们如何去枚举到不同的k个数呢?我们想想其实我们上文中提到枚举解决问题的时候就是一层层迭代,那么第一层从第一个数开始,第二层从第二个数开始,第三层从第三个数......一直到第k层从第k个数字开始。。。
(禁止套娃)
所以我们递归的话需要的参数其实就三个:
(1)当前枚举到了第几个数(说白了就是现在在第几层循环)
(2) 当前已经枚举了多少个数(因为题目说明了要从n个数中选k个数,所以如果已经枚举了k个数了,那就要回溯了)
(3) 当前位置的和是多少,因为题目要求是求和,那么我们就用一个参数来记录当前位置的和,这样成功枚举了k个数之后可以很快的验证当前的和sum是否符合题目要求(sum是素数)
判断素数应该不用多说吧,直接从2开始枚举即可,但是中间细节要注意优化,对于数字n判断是否为素数,只需要枚举到根号n即可
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
就像这样,时间复杂度就是根号n的成程度,大大减少了计算次数~
这样我们就完成了这个题目的解题思路,现在来看代码。
解题代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e2 + 10;
int nums[MAXN];
int n, k, ans = 0;
//判断是否为素数
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
//能整除证明n不是素数
if (n % i == 0) return false;
}
//一直没找到证明n的因数只有1和n
return true;
}
//开始深搜
void dfs(int ind, int num, int sum) {
//已经搜了k个数,开始回溯
if (num == k) {
//判断是否为素数
if (isPrime(sum)) ans++;
return ;
}
//从当前层数开始递归向下(可以看作进入下一层循环)
for (int i = ind; i <= n; i++) {
dfs(i + 1, num + 1, sum + nums[i]);
}
return ;
}
int main(){
cin >> n >> k;
for (int i = 1; i <= n; i++) scanf("%d", nums + i);
dfs(1, 0, 0);
cout << ans << endl;
return 0;
}
总结
这个题看着好像有点不好入手,其实把它想成k层循环就是一个玩具题目,O(∩_∩)O,事实上就是几步:
(1)从n个数中选取不同的k个数并求和
(2)判断当前sum是否为素数,是的话结果就加一
好了, 这个题题解就说到这,希望对你们有点帮助~