折半搜索&回溯法

介绍:

回溯法可见DFS(深度优先搜索)&回溯法https://blog.csdn.net/lebrce/article/details/145626088

折半搜索是搜索的一种技巧,在某些问题中可以大幅度减小时间复杂度。折半搜索采用了分治的思想,将整个数据范围拆成两部分分别进行搜索,最后再将两部分的答案合并成整个数据范围的答案。

复杂度分析

 回溯法:

时间复杂度:O(2^N),因为需要尝试集合中所有可能的元素组合。

空间复杂度:O(N),主要用于递归调用栈的空间。

 折半枚举法:

时间复杂度:O(2^{N/2} \log(2^{N/2}) = O(N \cdot 2^{N/2})\),其中2^{N/2}是枚举两部分所有可能组合的时间复杂度,log(2^{N/2})是二分查找的时间复杂度。

空间复杂度:O(2^{N/2}),主要用于存储两部分所有可能的和。 折半枚举法在集合大小较大时效率更高。



题目描述


小明拥有一个大小为N集合S,S中的元素依次为s1, s2, ..., Sn.....
给出一个数X,请你判断是否能从S中挑选任意个元素使得它们的和为X。


输入描述
第一行输入两个正整数N,X。接下来一行包含N个整数,s1,s2,.........表示集合的元素。
1<N<36, 1<si<10^16, 1<X<10^16。


输出描述
输出共T行,每行表示一组数据的答案。若可以拼凑出X,则输出Y,否则输出N。

回溯法解法:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// 全局变量部分
// n 用于存储输入整数的数量
int n;
// s 数组用于存储输入的整数,数组大小设为 37,索引从 1 开始使用
ll s[37];
// x 是我们要寻找的目标和,即子集中所有元素相加要达到的值
ll x;
// sum 在深度优先搜索过程中,用于记录当前正在考虑的子集的元素总和
ll sum;

// 深度优先搜索函数,用于判断是否能找到满足和为 x 的子集
// dep 表示当前正在处理第几个整数,sum 是当前子集的元素和
bool dfs(int dep, ll sum) {
    // 如果当前子集的元素和等于目标和 x,说明找到了符合条件的子集,返回 true
    if (sum == x) return true;

    // 以下是两种无法继续找到符合条件子集的情况
    // 情况一:当 dep 等于 n + 1 时,意味着已经遍历完了所有输入的整数,没有更多元素可供选择了
    // 情况二:当当前子集的元素和 sum 已经超过了目标和 x,那么后续再添加元素只会让和更大,也不可能找到符合条件的子集了
    // 这两种情况下都返回 false
    if (dep == n + 1 || sum > x) {
        return false;
    }

    // 这里是递归调用部分,有两种分支情况
    // 第一种:不选择当前第 dep 个整数,直接考虑下一个整数,即继续调用 dfs 函数,此时 dep 加 1,sum 保持不变
    // 第二种:选择当前第 dep 个整数,将其加入到当前子集和中,然后继续考虑下一个整数,即 dep 加 1,sum 加上 s[dep]
    // 只要这两种情况中有任意一种最终能找到和为 x 的子集,整个函数就返回 true,所以使用逻辑或运算符 || 连接这两个递归调用
    return dfs(dep + 1, sum) || dfs(dep + 1, sum + s[dep]);
}

int main() {
    // 从标准输入读取整数的数量 n 和目标和 x
    cin >> n >> x;

    // 循环从标准输入读取 n 个整数,并依次存储到 s 数组中,索引从 1 开始
    for (int i = 1; i <= n; i++) cin >> s[i];

    // 调用 dfs 函数开始深度优先搜索,从第一个整数(dep = 1)开始,初始子集和为 sum(通常初始为 0)
    // 如果 dfs 函数返回 true,说明找到了和为 x 的子集,输出 "Y"
    // 否则,说明没有找到,输出 "N"
    if (dfs(1, sum)) cout << "Y";
    else cout << "N";

    return 0;
}

  • 性能问题:由于时间复杂度是指数级的 ,当输入的整数数量 n 比较大时,程序的运行时间会急剧增加,可能会导致程序运行很慢甚至超时。在处理大规模数据时,需要考虑使用更高效的算法,如双向搜索或动态规划。

折半枚举法 :

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 定义全局变量
// n 表示输入的整数的数量
int n, n1, n2;
// s1 和 s2 分别用于存储将输入数据分成两部分后各自部分的元素
// 由于数据规模不大,最大设为 37 可以满足一般情况
ll s1[37], s2[37];
// x 是目标和,即我们要找的子集和需要等于这个值
ll x;
// sum 用于在深度优先搜索过程中记录当前子集元素的和
ll sum;
// ans 用于记录满足子集和等于目标值 x 的子集组合的数量
ll ans;

// 使用 map 来存储第一部分所有可能的子集和及其出现的次数
// 键是子集和,值是该子集和出现的次数
map<int, int> mp;

// 深度优先搜索函数,用于生成第一部分所有可能的子集和
// dep 表示当前正在处理的元素的索引,sum 表示当前子集的和
void dfs1(int dep, ll sum) {
    // 如果 dep 大于 n1,说明已经遍历完第一部分的所有元素
    if (dep > n1) {
        // 将当前子集和作为键,其出现次数加 1 存入 map 中
        mp[sum]++;
        return;
    }
    // 不选择当前元素,继续处理下一个元素
    dfs1(dep + 1, sum);
    // 选择当前元素,将其加入到当前子集和中,然后继续处理下一个元素
    dfs1(dep + 1, sum + s1[dep]);
}

// 深度优先搜索函数,用于生成第二部分所有可能的子集和
// 并检查是否能和第一部分的某个子集和组合得到目标和 x
// dep 表示当前正在处理的元素的索引,sum 表示当前子集的和
void dfs2(int dep, ll sum) {
    // 如果 dep 大于 n2,说明已经遍历完第二部分的所有元素
    if (dep > n2) {
        // 检查 map 中是否存在值为 x - sum 的子集和
        // 如果存在,将其出现的次数累加到 ans 中
        ans += mp[x - sum];
        return;
    }
    // 不选择当前元素,继续处理下一个元素
    dfs2(dep + 1, sum);
    // 选择当前元素,将其加入到当前子集和中,然后继续处理下一个元素
    dfs2(dep + 1, sum + s2[dep]);
}

int main() {
    // 关闭输入输出流的同步,提高输入输出效率
    ios_base::sync_with_stdio(0);
    // 读取输入的整数数量 n 和目标和 x
    cin >> n >> x;
    // 将输入的 n 个元素分成两部分,n1 为前半部分的元素数量
    n1 = n / 2;
    // n2 为后半部分的元素数量
    n2 = n - n1;

    // 读取前半部分的元素,存入 s1 数组
    for (int i = 1; i <= n1; i++) cin >> s1[i];
    // 读取后半部分的元素,存入 s2 数组
    for (int i = 1; i <= n2; i++) cin >> s2[i];

    // 调用 dfs1 函数生成第一部分所有可能的子集和,并存储到 map 中
    dfs1(1, 0);
    // 调用 dfs2 函数生成第二部分所有可能的子集和,并检查是否能和第一部分组合得到目标和
    dfs2(1, 0);

    // 如果 ans 为 0,说明没有找到满足条件的子集,输出 N
    if (ans == 0) cout << "N";
    // 否则,说明找到了满足条件的子集,输出 Y
    else cout << "Y";

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值