-P1036 [NOIP2002 普及组] 选数-
[输入样例]
4 3
3 7 12 19
[输出样例]
1
[解题思路]
给我们n个数,让我们任选其中k个数并求和,求使这个和为素数的组合共有多少种。对于这种多选多的模型,我们可以用dfs的方法递归求出符合条件的总数。
需要注意的是,如果两次选取的数相同,则他们的和也相同,这应该认为是同一种情况,因此,我们必须要想一个办法,保证递归过程中无重复的选取组合。
具体实现方式如下:我们枚举第一次选取的数,接下来选取的每个数都满足在数组中的位置大于前一个数,这样,我们就能不重复地完成所有组合的遍历。
接下来就是代码部分:
#include<vector>
#include<iostream>
using namespace std;
//判断是否为素数
bool isPrime(int n)
{
bool ret = true;
for (int i = 2; i * i <= n; i++)
{
if (n % i == 0)
{
ret = false;
break;
}
}
return ret;
}
//传入参数:数组arr、还需继续选取个数k、所选定的数之和sum(缺省值为0)、开始搜索下一个数的起始位置pos(缺省值为0)
int dfs(vector<int> arr, int k, int sum = 0, int pos = 0)
{
//如果无需继续选取数,则判断总和是否为素数,直接返回isPrime的结果
if (k == 0)
{
return isPrime(sum);
}
//若k>0,则继续选取下一个数
int ret = 0;
for (int i = pos; i < arr.size(); i++)
{
//调用递归选取下一个数时需要改变的参数:还需继续选取的个数k减去1,sum加上选取的这个数的值,下一次搜索的起始位置为本次搜索的位置+1
ret += dfs(arr, k - 1, sum + arr[i], i + 1);
}
return ret;
}
int main()
{
//参数读取及arr的声明和初始化
int n, k;
cin >> n >> k;
vector<int> arr;
while (n--)
{
int t;
cin >> t;
arr.push_back(t);
}
//调用dfs并打印
cout << dfs(arr, k);
return 0;
}
接下来,我们来看一道可以用类似解法解出的题目。
-P1025 [NOIP2001 提高组] 数的划分-
[输入样例]
7 3
[输出样例]
4
[详解]
#include<iostream>
using namespace std;
//传入参数:需要分割的数num,剩余需要分割的数目k,上一次分割出的数
int dfs(int num, int k, int pre_part = 1)
{
//若需要分割的数目为1,则说明该分割方式有效,返回1
if (k == 1) return 1;
//若num小于需要分割的个数,则说明该方案不可行,返回0
if (num < k) return 0;
//若还能继续分割,进行递归计算有效分割个数
int ret = 0;
//这里解释一下为什么从pre_part开始枚举:由于1,1,5、1,5,1、5,1,1属于同一种分割,
//为了避免分割方式重复,我们只要保证每一次后分割出的数大于等于前一次分割出的数即可
//同时,由于我们分割出的数满足非严格递增,我们枚举第一次分割出的数一定小于等于num/2
for (int i = pre_part; i <= num / 2; i++)
{
ret += dfs(num - i, k - 1, i);
}
return ret;
}
int main()
{
int num, k;
cin >> num >> k;
cout << dfs(num, k, 1);
return 0;
}