搜索&&折半搜索

搜索例题

今天,孙培轩老师(没听说过),来给我们上了一节公开催眠课,结果看起来跟上次周镇东讲的一样繁琐,没想到就是个纸老虎,讲的是普及组的知识,非常简单喵
由于普通搜索是基础组难度,过于简单,所以这里直讲一道经典例题——小猫爬山喵


对于每只小猫,可以有两类选择喵

  1. 进入其它已经有猫咪的缆车喵
  2. 再每一个缆车,去这个缆车

那么,我们可以用一个for 循环枚举每一个已有的缆车,看看能不能进去,可以则进去继续搜索喵,注意后面还需要特别的判断新开一个缆车的情况喵

那么,搜索思路就很清晰了喵,对于每个参数numcnt,分别表示现在在给第num只猫分配缆车,现在有cnt个已经卖了的缆车喵

接着就枚举每一个已有的缆车,看看猫咪能不能挤进去,能就试一试喵

枚举完后不要忘记,还要考虑新买一个缆车的情况喵

还有,这题需要加上一个剪枝,就是如果现在的费用超过了之前的最小总费用,那么这个方法一定很坏坏~~喵

code
res是用来记录缆车的喵)

void dfs(int num, int cnt){
	if(num > n){
		ans = min(ans, cnt);
		return;
	}
	if(cnt >= ans) return;
	for(int i = 1 ; i <= cnt ; i++){
		if(res[i] + c[num] <= w){
			res[i] += c[num];
			dfs(num + 1, cnt);
			res[i] -= c[num];
		}
	}
	res[cnt + 1] += c[num];
	dfs(num + 1, cnt + 1);
	res[cnt + 1] -= c[num];
}

好了,本题讲解结束,我接下来会控制自己尽量不说“喵”的,喵

折半搜索

折半搜索,和二分查找用着共同的思想

顾名思义,折半搜索,就是把给定的物质折成前后长度相同的两半分别搜索,常用于解决状态空间较大的问题,如组合优化、排列组合等问题。

折半搜索的运用场景

  • 子集和问题
  • 组合问题
  • 排列问题
  • 分治类问题
  • 状态空间较大的搜索问题

算法特点

  1. 折半思想体现
  • 将问题分解为两部分处理
  • 通过剪枝减少搜索空间
  • 排序后可以提前终止不必要的搜索路径
  1. DFS特点
  • 递归实现
  • 需要回溯
  • 深度优先探索
  1. 优化技巧
  • 排序输入数据以便剪枝
  • 记录已访问状态避免重复计算
  • 提前终止不可能的分支

复杂度分析

  • 时间复杂度:通常为 O(2^n) 量级,但通过折半剪枝可以大幅减少实际计算量
  • 空间复杂度O(n) - 取决于递归深度

这种DFS折半搜索技术在算法竞赛和面试中经常出现,特别是在需要穷举所有可能解但又需要高效剪枝的场景中非常有用。

例题讲解



如果数据范围小一点,可以用暴搜和背包过

但是可惜,数据范围特大,背包会MLE,暴搜会TLE

我们可以在暴搜的基础上优化成折半搜索,从而减少时间复杂度,避免时间超限

其实并没有那么难,就是把前后两段分成连个dfs来做,随后随便用一个upper_bound优化即可

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int N = 50;
int n, t;
int a[N];
int res;
vector <int> v, v2;
void dfs(int num, int cnt) {
	if (num > n / 2) {
		v.push_back(cnt);
		return;
	}
	dfs(num + 1, cnt + a[num]);
	dfs(num + 1, cnt);
}
void dfs2(int num, int cnt) {
	if (num > n) {
		int sum = upper_bound(v.begin(), v.end(), t - cnt) - v.begin() - 1;
		if (v[sum] + cnt <= t) res = max(res, v[sum] + cnt);
		return;
	}
	dfs2(num + 1, cnt + a[num]);
	dfs2(num + 1, cnt);
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> t;
	for (int i = 1 ; i <= n ; i++) {
		cin >> a[i];
	}
	dfs(1, 0);
	sort(v.begin(), v.end());
	dfs2(n / 2 + 1, 0);
	cout << res << endl;
	return 0;
}


也是模板题,和上一题代码没什么区别,只是在记录答案是是res += sum + 1

code

void dfs(int num, int cnt) {
	if (num > n / 2) {
		v.push_back(cnt);
		return;
	}
	dfs(num + 1, cnt + a[num]);
	dfs(num + 1, cnt);
}
void dfs2(int num, int cnt) {
	if (num > n) {
		int sum = upper_bound(v.begin(), v.end(), t - cnt) - v.begin() - 1;
		if (v[sum] + cnt <= t) res += sum + 1;
		return;
	}
	dfs2(num + 1, cnt + a[num]);
	dfs2(num + 1, cnt);
}


还是模板题,只是包含了亿点点思维,我们要加的和就是对1~n的高斯求和的值除以 2 ,注意,这里最后得到的值要除以二,因为他是两段子序列,比如1 2 3 4,按理来说答案是1,但是实际输出了2,这是因为只有一个分割就是在把2和3分在一起、1和4分在一起,但是他会检测到这两个子序列,所以要用总和除以二

还有,答案的加法又变成了res += sum

最后注意一点,题目有特殊数据点,可能这 n个数根本没法分割成相同的两端(即所有数的和不是2的倍数),搜索就会出错,要特殊处理

code

void dfs(int num, int cnt) {
	if (num > n / 2) {
		v.push_back(cnt);
		return;
	}
	dfs(num + 1, cnt + a[num]);
	dfs(num + 1, cnt);
}
void dfs2(int num, int cnt) {
	if (num > n) {
		int sum = upper_bound(v.begin(), v.end(), t - cnt) - lower_bound(v.begin(), v.end(), t - cnt);
		res += sum;
		return;
	}
	dfs2(num + 1, cnt + a[num]);
	dfs2(num + 1, cnt);
}


ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	for (int i = 1 ; i <= n ; i++) {
		a[i] = i;
		t += i;
	}
	if (t % 2 == 1) {
		cout << 0;
		return 0;
	}
	t /= 2;
	dfs(1, 0);
	sort(v.begin(), v.end());
	dfs2(n / 2 + 1, 0);
	cout << res / 2 << endl;
1 个赞

论坛上咋全是猫娘

没有啊