P1036 选数题解

题目传递门

写在最前:
暴搜是入门算法的必修课程,一般是青少年蓝桥杯赛的第4题左右的水平,如果做出来,在吉林省这样的省份,基本就是一等奖的水平。

暴搜又分为深度优先和广度优先,其中入门级选手对于深度优先不好理解,我试图以最清晰的思路引领同学们突破这个问题。

1、结束的条件

深度优先搜索本质上是递归,递归必然要求有出口,否则就是死循环,所以,深度优先想要考虑出口是什么。

2、参数的设置

我们不是以第1个或最后1个做为思考对象,通常是以中间某个普遍的位置进行思考,一般称为第(i)个,或第(i)步,这里最关键的问题是参数有哪些!一般有一个位置或者步数,这是必备的。其它的根据题意来配置,比如有数量上限,就需要增加一个当前数量;比如有和是不是质数,就需要携带和这个概念。

3、下步的选择

我们试图找出(i)步和(i+1)步的关系。用一句短点的话来描述:“下一步才是我们可操作的!!
比如:下一步可以通过一个循环的方法,尝试所有可能的,也可能像01背包一样,下一轮选择物品或者下一轮不选择物品,两种情况,演化成了两个分支,生个分支走到头,都会可能存在一组解,在本岔路口,需要把两组解加在一起才是本路口的所有解。

下面,我们通过三个步骤来解这道题:

1、结束的条件

(1)选择完了所有的数字,对应(n),还没有找到和为质数。
(2)已经选择够了(k)个数字,就需要判断和是不是质数了,是就输出不是也没有必要继续了。

2、参数的设置

(1) 走到第几个数面前,(step)
(2) 目前的和是多少了,这个要求一路检查和是不是质数,不带和这个概念不行。---来自于题意
(3) 一共(k)个数,现在用了几个了。---来自于题意

3、下步的选择

都是站在现在看未来,未来才是我们准备操作的关键,注意,不是现在啊!
下一个数字,我们可以选择要,还是不要。这是两个分支,我们的任务就是把此子任务发给两个人,让他们分别去做,分别回来汇报,然后我们再把两个方向的数值加在一起,再向上汇报。

#include <bits/stdc++.h>

using namespace std;
const int N = 30;
int a[N];
int n, k;

//是不是质数
bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i <= n / i; i++)
        if (n % i == 0) return false;
    return true;
}


/**
 * 功能:获取可行方法数
 * @param step  走在第几个箱子面前
 * @param sum   已经获得的数字和
 * @param m     已经选择了几个数字
 * @return      获取可行方法数
 */
int dfs(int step, int sum, int m) {
    //边界状态
    if (step == n + 1) return 0;//都走冒了还没有停止,说明不可能有结果了
    if (m == k) return isPrime(sum);//已经完成选择k个数,判断和是不是质数.如果是质数,说明找到了一种合法解

    //解析:
    //1、dfs(step + 1, sum + a[step + 1], m + 1)
    //当前的箱子里面的数字要着,然后走到下一个箱子面前,这样,有三个东西发生了变化
    //分别是 面对的箱子是哪个,数字总和,已选择的总数

    //2、dfs(step + 1, sum, m)
    //当前的箱子里面的数字不要,然后走到下一个箱子面前,这样,有一个东西变生了变化
    //面对的箱子是哪个
    return dfs(step + 1, sum + a[step + 1], m + 1) + dfs(step + 1, sum, m);
}

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //之所以把step初始值设置为0,可以理解为提前观察,决策后分支.看好了再走路,而不是走了路再看看好不好走。
    cout << dfs(0, 0, 0);
    return 0;
}

二进制枚举的方法也粘贴上一个方法吧:

#include <bits/stdc++.h>

using namespace std;
const int N = 30;
int a[N];

//是不是质数
bool isPrime(int n) {
    for (int i = 2; i <= n / i; i++)
        if (n % i == 0) return false;
    return true;
}

int main() {
    int n, k, ans = 0;
    cin >> n >> k;
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);

    int U = 1 << n; //U-1即为全集 ,比如 1<<5 就是 2的5次方,就是32,U=32。而U-1=31,就是表示 1 1 1 1 1
    for (int S = 0; S < U; S++) { //枚举所有子集[0,U)
        //找到k元子集
        if (__builtin_popcount(S) == k) {
            int sum = 0;
            //是哪些数存在于子集中呢?
            for (int i = 0; i < n; i++) {
                int bit = (S >> i) & 1;
                if (bit) sum += a[i]; //遍历数字S的每一位,如果不是0,表示这一位上的数字是存在的,需要加进来
            }
            //判断子集元素和是不是素数
            if (isPrime(sum)) ans++;
        }
    }
    cout << ans << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15006108.html