题目链接:https://www.luogu.com.cn/problem/P1036

题目描述

已知 \(n\) 个整数 \(x_1,x_2,…,x_n\) ,以及 \(1\) 个整数 \(k(k<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<n)\)

\(x_1,x_2,…,x_n (1 \le x_i \le 5000000)\)

输出格式

屏幕输出,格式为: \(1\)个整数(满足条件的种数)。

样例输入1

4 3
3 7 12 19

样例输出1

1

题解

搜索解法

首先,我们可以把这个问题拆分成两个子问题:

  1. 从 \(n\) 个数中选出 \(k\) 个数;
  2. 判断选出的 \(k\) 个数之和是不是素数。

其中第2个子问题很好处理,我们只需要开一个 check(int num) 函数判断 num 是不是素数即可。

判断素数 代码段:

bool check(int num) {
if (num < 2) return false; // 小于2的数不是素数
for (int i = 2; i * i <= num; i ++) { // 从2到根号n遍历i
if (num % i == 0) // 如果i能够整除num,说明num不是素数
return false;
}
return true;
}

然后我们再回过头来分析第1个问题——从 \(n\) 个数中选出 \(k\) 个数。

我们假设给我们的 \(n\) 个数是 \(a[0],a[1],a[2], \dots , a[n-1]\) ,则:

我们可以开函数 dfs(int id, int cnt, int sum) 来表示:

  • 我当前放到了第 \(id\) 给位置(所以我现在正在思考要不要选择 \(a[id]\));
  • 我已经选择了 \(cnt\) 个元素;
  • 我已经选择了的这 \(cnt\) 个元素之和是 \(sum\) 。

所以,如果当前已经满足 \(cnt=k\) ,则说明我已经选择了 \(k\) 个元素,此时只需要判断 \(sum\) 是不是素数(是素数则答案\(+1\))并return。

否则,如果当前 \(id \ge n\) ,说明前面 \(n\) 个元素都选了也没有选够 \(k\) 个元素,则直接return。

否则(\(id \lt n\) 且 \(cnt \lt k\)),则我当前面临两个选择:

  • 选择第 \(id\) 个元素,此时进行 dfs(id+1, cnt+1, sum+a[id])
  • 不选择第 \(id\) 个元素,此时进行 dfs(id+1, cnt, sum)

实现代码如下:

#include <bits/stdc++.h>
using namespace std;
bool check(int num) {
if (num < 2) return false; // 小于2的数不是素数
for (int i = 2; i * i <= num; i ++) { // 从2到根号n遍历i
if (num % i == 0) // 如果i能够整除num,说明num不是素数
return false;
}
return true;
}
int n, k, a[22], res; // res用于存储答案
void dfs(int id, int cnt, int sum) {
if (cnt == k) {
if (check(sum)) res ++;
return;
}
if (id >= n) return;
dfs(id+1, cnt+1, sum+a[id]);
dfs(id+1, cnt, sum);
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i ++) cin >> a[i];
dfs(0, 0, 0);
cout << res << endl;
return 0;
}

当然,我们也可以进行一下下优化。

因为我们知道我们这里的搜索是使用的 深度优先搜索 算法。而深搜的最基础的优化是 剪枝 ,即:

既然我已经知道这么搜下去没有结果了,那么我就不会继续进行深度优先搜索了。

那么这里可以怎么剪枝呢?

我们假设当前我们在选择第 \(id\) 个数,此时一共选择了 \(cnt\) 个数。

那么这个时候我一共还有 \(n-id\) 个数可以选,而此时还需要选 \(k-cnt\) 个元素需要选,所以如果 \(n-id \lt k-cnt\) ,那么就选我接下来 \(n-id\) 个元素都选,我都凑不够 \(k\) 个数,所以我就不需要进行下去了。

这就是这里的深搜的剪枝优化(不过这道题目数据量比较小所以不剪枝也能过)。

实现的时候只需要在 dfs 函数的一开始加上如下这行代码即可:

    if (n-id < k-cnt) return;

状态压缩+枚举解法

(注:请不要将 状态压缩 想得很难,它其实就是将一个数的二进制形式有一个状态之间进行一一对应)

我们知道深度优先搜索其实就是以递归的形式将情况进行了一遍枚举。

那么有没有别的办法进行枚举呢?

其实对于数组 \(a[0],a[1], \dots , a[n-1]\) ,我们可以从 \(0\) 到 \(2^n\) 去枚举每一个数(我假设我用一个变量 \(s\) 来表示这个数)。

那么我们可以知道这个 \(s\) 在二进制的情况下是刚好有 \(n\) 位的,它的第 \(i\) 位是否为1对应着是否选择了 \(a[i]\) :

  • 如果 \(s\) 的二进制第 \(i\) 位为 \(1\) ,则表示选择了 \(a[i]\);
  • 如果 \(s\) 的二进制第 \(i\) 位为 \(0\) ,则表示没有选择 \(a[i]\)。

于是,我们需要处理的细节就变成了:

  1. 判断 \(s\) 的二进制表示中是不是恰好有 \(k\) 位为 \(1\);
  2. 判断 \(s\) 的二进制表示中有哪些数位对应为 \(1\)。

对于第 \(2\) 个问题,假设我们想知道 \(s\) 的第 \(i\) 为是不是为1,我们只需要判断 s & (1<<i) 是否不为零;或者判断 (s>>i) & 1 是否不为零即可。

对于第 \(1\) 个问题,我们可以自己写一个函数来进行计算,比如这样:

bool check(int s) {
int cnt = 0;
for (int i = 0; i < n; i ++)
if (s & (1<<i)) // 或者 (s>>i) & 1 亦可
cnt ++;
return cnt;
}

但是C++也提供给了我们一个函数用于直接获得一个数对应的二进制中有多少位为1,就是“__builtin_popcount” 函数(注意:最前面是2个下划线横杠)。

它的使用方法如下(省略了头文件):

#include <bits/stdc++.h>
using namespace std;
int main() {
cout << __builtin_popcount(3) << endl; // 3二进制11,所以输出2
cout << __builtin_popcount(9) << endl; // 9二进制1001,所以输出2
cout << __builtin_popcount(13) << endl; // 13二进制1101,所以输出3
return 0;
}

然后,我们就可以使用状态压缩的方式进行枚举,然后判断它们的和是不是素数就可以了。

实现代码如下:

#include <bits/stdc++.h>
using namespace std;
bool check(int num) { // 判断素数
if (num < 2) return false;
int a = sqrt(num); // 这里用sqrt的方式先求出来,不要将sqrt用在循环里面,因为数学函数比较慢
for (int i = 2; i <= a; i ++)
if (num % i == 0)
return false;
return true;
}
int n, k, a[22], res; // res用于表示答案
int main() {
cin >> n >> k;
for (int i = 0; i < n; i ++) cin >> a[i];
for (int s = 0; s < (1<<n); s ++) {
if (__builtin_popcount(s) == k) {
int sum = 0;
for (int i = 0 ; i < n; i ++) {
if (s & (1<<i))
sum += a[i];
}
if (check(sum)) res ++;
}
}
cout << res << endl;
return 0;
}

洛谷P1036 选数 题解 简单搜索/简单状态压缩枚举的更多相关文章

  1. 【搜索】【入门】洛谷P1036 选数

    题目描述 已知 n个整数x1​,x2​,…,xn​,以及1个整数k(k<n).从nn个整数中任选kk个整数相加,可分别得到一系列的和. 例如当n=4,k=3,4个整数分别为3,7,12,19时, ...

  2. 洛谷 P1036 选数

    嗯.... 这种类型的题在新手村出现还是比较正常的, 但是不知道为什么它的分类竟然是过程函数与递归!!!(难道这不是一个深搜题吗??? 好吧这就是一道深搜题,所以千万别被误导... 先看一下题目: 题 ...

  3. 【洛谷P1036 选数】

    这个题显然用到了深搜的内容 让我们跟着代码找思路 #include<bits/stdc++.h>//万能头 ],ans; inline bool prime(int n)//最简单的判定素 ...

  4. 洛古P1036 选数 题解

    [我是传送门] 这是一道很经典的深搜与回溯(难度一般) 可是就这个"普及-" 让本蒟蒻做了一晚上+半个上午(实际我不会深搜回溯,全靠框架+去重); 下面让我分享下本蒟蒻的(全排列+ ...

  5. (水题)洛谷 - P1036 - 选数

    https://www.luogu.org/problemnew/show/P1036 $n$ 才20的数据量,我当时居然还在想怎么分组组合,直接 $2^{20}$ 暴力搞就行了. $x_i $太大了 ...

  6. 洛谷——P1036 选数

    题目描述 已知 n 个整数 x1,x2,…,xn,以及一个整数 k(k<n).从 n 个整数中任选 k 个整数相加,可分别得到一系列的和.例如当 n=4,k=3,4 个整数分别为 3,7,12, ...

  7. 洛谷P1036.选数(DFS)

    题目描述 已知 n个整数 x1,x2,-,xn,以及11个整数k(k<n).从n个整数中任选k个整数相加,可分别得到一系列的和.例如当n=4,k=3,4个整数分别为3,7,12,19时,可得全部 ...

  8. 洛谷P1036选数(素数+组合数)

    题目链接:https://www.luogu.org/problemnew/show/P1036 主要考两个知识点:判断一个数是否为素数.从n个数中选出m个数的组合 判断一个数是否为素数: 素数一定是 ...

  9. 洛谷P1036 选数

    题目描述 已知 n 个整数 x1,x2,…,xn,以及一个整数 k(k<n).从 n 个整数中任选 k 个整数相加,可分别得到一系列的和.例如当 n=4,k=3,4 个整数分别为 3,7,12, ...

随机推荐

  1. hdu 1289 Hat’s IEEE

    Problem - 1289 好题.其实就是模拟IEEE754的格式,不过要注意的是,这里用的32位是float,用double就不对了. 代码如下: #include <cstdio> ...

  2. Activiti快速入门项目-kft-activiti-demo

    1.项目简介 1.1 项目信息 本项目旨在让Activiti初学者可以快速入门,使用工作流里面的请假流程作为Activiti企业实战的Hello World. 简单通过这个实例说明如何结合流程与业务, ...

  3. javascript 容易混淆遗忘的基础知识

    1.  标识符     所谓标识符,就是指变量.函数.属性的名字,或者函数的参数.标识符可以是按照下列格式规则组合起来的一或多个字符:     1.1   第一个字符必须是一个字母.下划线( _ )或 ...

  4. gradle在build的时候找不到某个jar包的解决办法

    前几天公司来新人, 我给他装项目环境的时候遇到一个问题, 在执行gradle build时遇到一系列的错误, 我一个个分析并解决了, 特此记录, 以供他人参考. 一, 首先遇到了找不到spring-b ...

  5. Asp.net MVC中如何获取控制器的名称

    如果在代码中 当前controller.action的获取RouteData.Route.GetRouteData(this.HttpContext).Values["controller& ...

  6. Java 简单校验框架

    数据校验框架现状 在我们的方法入口后面,难免会有如下样子的代码: result.setSuccess(false); if (StringUtils.isBlank(bizOrder.getThird ...

  7. TabHost选项卡的实现(二):使用Fragment实现

    在上一篇博客<TabHost选项卡的实现(一):使用TabActivity实现>中,讲解了如何使用TabActivity创建管理选项卡,但是,通过TabActivity创建选项卡的方式已经 ...

  8. UVa11400 - Lighting System Design——[动态规划]

    题干略. 题意分析: 很容易理解一类灯泡要么全部换要么全不换,其实费用节省的主要原因是由于替换灯泡类型而排除了低压电压源,于是我们就可以推断出灯泡类型替换的原则: 对于两类灯泡a1和a2,a1可以被a ...

  9. 2016.1.22 扩充临时表空间解决ora-01652错误

    今天运行一个复杂查询时报错ora-01652 无法通过128 扩展temp段, 网上说是临时表空间大小不够,运行了脚本调整临时表空间,问题解决 alter database tempfile '/ap ...

  10. hdu 1254 推箱子(嵌套搜索,bfs中有dfs)

    推箱子 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submiss ...