Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
一道看似暴力实则要动脑筋的题
题目描述
已知 个整数 ,以及 个整数 。从 个整数中任选 个整数相加,可分别得到一系列的和。例如当 个整数分别为 时,可得全部的组合与它们的和为:
现在,要求你计算出和为素数共有多少种。
例如上例,只有一种的和为素数:.
输入格式
第一行两个空格隔开的整数 。
第二行 个整数,分别为 。
输出格式
输出一个整数,表示种类数。
输入输出样例
输入
4 3
3 7 12 19
输出
1
思路:真的有看上去这么简单吗?
大家第一眼看到这个题,就会发现,对对于题目的样例,直接来3个for 循环暴力遍历不就可以了吗???
然而,这其中的k是变化的。也就是说,k=4时,你要搞4个for循环,k=20时你要搞20个for循环...
不瞒你说还真有人这样做了:
if (k==2)
{
for (int i=1;i<=n-1;i++)
for (int i1=i+1;i1<=n;i1++)
if (zs(a[i]+a[i1])) s++;//计算
return;
}
...
if (k==5)
{
for (int i=1;i<=n-4;i++)
for (int i1=i+1;i1<=n-3;i1++)
for (int i2=i1+1;i2<=n-2;i2++)
for (int i3=i2+1;i3<=n-1;i3++)
for (int i4=i3+1;i4<=n;i4++)
if(zs(a[i]+a[i1]+a[i2]+a[i3]+a[i4]))s++;
return;
}
...
看到这种代码我只能说,凑代码行数算是给你整明白了(当然也没有嘲讽写这种代码的人的意思)
不用for循环解决遍历问题?
让我们祭出一个大杀器:递归。
我们观察一下遍历的过程:
用个下标来表示选中的数字。
如何遍历?通常的做法是:
固定的顺序,让 至 始终从左至右。 那么遍历的方法是,把 固定在所有可以占据的位置上,然后 在 右边的区间递归的遍历。
比如一个区间 , 如何遍历 ?那就是先令 为1,然后让问题等价于 在区间 , 如何遍历 ;再令 为 ,然后让问题等价于 在区间 , 如何遍历 ...知道 为 时再在区间 , 遍历 ,那么整个遍历结果就出来了。
这样想的话,我们就能够通过很多子问题来解决父问题了,这就意味着我们可以写递归函数了!
void recur(int i,int remain,int now){
if(remain==1){
for(;i<=n-remain;i++){
//if(!isnot[arr[i]+now])cnt++;
if(prime(arr[i]+now))cnt++;
}
return;
}
for(;i<=n-remain;i++){
recur(i+1,remain-1,now+arr[i]);
}
return;
}
其中i是遍历区间的起始点(重点是n-1),remain是还有多少个下标需要遍历,now是前面的数字固定时,遍历到这个子问题时已经确定的加和。
全部代码
#include<iostream>
#include<cmath>
using namespace std;
const int maxn=(int)1e8;
int arr[30];
int isnot[maxn];
int cnt;
int n,k;
int prime(int n){
if(n==2)return 1;
for(int i=2;i<=sqrt(n)+1;i++){
if(n%i==0)return 0;
}
return 1;
}
void recur(int i,int remain,int now){
if(remain==1){
for(;i<=n-remain;i++){
//if(!isnot[arr[i]+now])cnt++;
if(prime(arr[i]+now))cnt++;
}
return;
}
for(;i<=n-remain;i++){
recur(i+1,remain-1,now+arr[i]);
}
return;
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++){
cin>>arr[i];
}
recur(0,k,0);
cout<<cnt;
}