P1036 选数题解

题目传递门

一、深搜步骤

1、深搜结束的条件

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

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

2、参数的设置

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

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

3、下步的选择

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

下一个数字,我们可以选择要,还是不要。这是两个分支,我们的任务就是把此子任务发给两个人,让他们分别去做,分别回来汇报,然后我们再把两个方向的数值加在一起,再向上汇报。

二、不带回溯解法

#include <bits/stdc++.h>

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

//是不是质数
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      获取可行方法数
 */
void dfs(int step, int sum, int m) {
    //脚落地
    if (m == k && isPrime(sum)) {
        cnt++;
        return;//已经完成选择k个数,判断和是不是质数.如果是质数,说明找到了一种合法解
    }
    //这句需在放在后面
    if (step == n + 1) return;

    //选择当前数字
    dfs(step + 1, sum + a[step], m + 1);

    //放弃当前数字
    dfs(step + 1, sum, m);
}

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    dfs(1, 0, 0);
    cout << cnt << endl;
    return 0;
}

三、回溯解法

#include <bits/stdc++.h>

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

//判断一个数是不是质数
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      获取可行方法数
 */
void dfs(int step, int m) {
    //脚落地
    if (m == k) {
        if (isPrime(sum)) cnt++;
        return;//已经完成选择k个数,判断和是不是质数.如果是质数,说明找到了一种合法解
    }

    //这句需在放在后面
    if (step == n + 1) return;

    //选择当前数字
    sum += a[step];//回溯法
    dfs(step + 1, m + 1);
    sum -= a[step];//加多少返多少

    //放弃当前数字
    dfs(step + 1, m);
}

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    dfs(1, 0);
    cout << cnt << endl;
    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;
}
posted @ 2021-07-13 13:59  糖豆爸爸  阅读(97)  评论(0编辑  收藏  举报
Live2D