洛谷P1036 选数 题解 简单搜索/简单状态压缩枚举
题目链接: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
题解
搜索解法
首先,我们可以把这个问题拆分成两个子问题:
- 从 \(n\) 个数中选出 \(k\) 个数;
- 判断选出的 \(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]\)。
于是,我们需要处理的细节就变成了:
- 判断 \(s\) 的二进制表示中是不是恰好有 \(k\) 位为 \(1\);
- 判断 \(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 选数 题解 简单搜索/简单状态压缩枚举的更多相关文章
- 【搜索】【入门】洛谷P1036 选数
题目描述 已知 n个整数x1,x2,…,xn,以及1个整数k(k<n).从nn个整数中任选kk个整数相加,可分别得到一系列的和. 例如当n=4,k=3,4个整数分别为3,7,12,19时, ...
- 洛谷 P1036 选数
嗯.... 这种类型的题在新手村出现还是比较正常的, 但是不知道为什么它的分类竟然是过程函数与递归!!!(难道这不是一个深搜题吗??? 好吧这就是一道深搜题,所以千万别被误导... 先看一下题目: 题 ...
- 【洛谷P1036 选数】
这个题显然用到了深搜的内容 让我们跟着代码找思路 #include<bits/stdc++.h>//万能头 ],ans; inline bool prime(int n)//最简单的判定素 ...
- 洛古P1036 选数 题解
[我是传送门] 这是一道很经典的深搜与回溯(难度一般) 可是就这个"普及-" 让本蒟蒻做了一晚上+半个上午(实际我不会深搜回溯,全靠框架+去重); 下面让我分享下本蒟蒻的(全排列+ ...
- (水题)洛谷 - P1036 - 选数
https://www.luogu.org/problemnew/show/P1036 $n$ 才20的数据量,我当时居然还在想怎么分组组合,直接 $2^{20}$ 暴力搞就行了. $x_i $太大了 ...
- 洛谷——P1036 选数
题目描述 已知 n 个整数 x1,x2,…,xn,以及一个整数 k(k<n).从 n 个整数中任选 k 个整数相加,可分别得到一系列的和.例如当 n=4,k=3,4 个整数分别为 3,7,12, ...
- 洛谷P1036.选数(DFS)
题目描述 已知 n个整数 x1,x2,-,xn,以及11个整数k(k<n).从n个整数中任选k个整数相加,可分别得到一系列的和.例如当n=4,k=3,4个整数分别为3,7,12,19时,可得全部 ...
- 洛谷P1036选数(素数+组合数)
题目链接:https://www.luogu.org/problemnew/show/P1036 主要考两个知识点:判断一个数是否为素数.从n个数中选出m个数的组合 判断一个数是否为素数: 素数一定是 ...
- 洛谷P1036 选数
题目描述 已知 n 个整数 x1,x2,…,xn,以及一个整数 k(k<n).从 n 个整数中任选 k 个整数相加,可分别得到一系列的和.例如当 n=4,k=3,4 个整数分别为 3,7,12, ...
随机推荐
- @codeforces - 117C@ Cycle
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个竞赛图(有向完全图),请找出里面的某个三元环,或者判断不 ...
- day6_python之pickle、shelve序列化和反序列化
pickle.shelve,python私有,支持所有python数据类型 一.pickle dic={'name':'egon','age':18} print(pickle.dumps(dic)) ...
- 容器服务kubernetes federation v2实践五:多集群流量调度
概述 在federation v2多集群环境中,通过前面几篇文章的介绍,我们可以很容易的进行服务多集群部署,考虑到业务部署和容灾需要,我们通常需要调整服务在各个集群的流量分布.本文下面简单介绍如何在阿 ...
- Laravel5.2 发送邮件(smtp方式最简单的讲解!)-邮件部分
https://blog.csdn.net/wulove52/article/details/71172842 Laravel集成了SwiftMailer库进行邮件发送,邮件配置文件位于config/ ...
- jsp中文乱码六种情况---解决方案
转 jsp中文乱码六种情况---解决方案 2016年10月22日 21:32:55 阅读数:10672 来源:http://blog.csdn.net/lovesummerforever/articl ...
- Python __call__详解
可以调用的对象 关于 __call__ 方法,不得不先提到一个概念,就是可调用对象(callable),我们平时自定义的函数.内置函数和类都属于可调用对象,但凡是可以把一对括号()应用到某个对象身上都 ...
- Editplus配置java编译运行环境
1.进入配置环境界面 首先,从菜单"工具(Tools)"->"配置用户工具..."进入用户工具设置. 在类别里展开"工具"树形菜单-& ...
- HTML DOM clearInterval() 方法
定义和用法 clearInterval() 方法可取消由 setInterval() 设置的 timeout. clearInterval() 方法的参数必须是由 setInterval() 返回的 ...
- codeforce 378 div 2 F —— Drivers Dissatisfaction (最小生成树,LCA,倍增)
官方题解: If you choose any n - 1 roads then price of reducing overall dissatisfaction is equal to min(c ...
- linux 编译模块
第一步, 我们需要看一下模块如何必须被建立. 模块的建立过程与用户空间的应用程序的 建立过程有显著不同; 内核是一个大的, 独立的程序, 对于它的各个部分如何组合在一起 有详细的明确的要求. 建立过程 ...