首页 > 其他分享 >折半搜索 学习笔记

折半搜索 学习笔记

时间:2023-09-02 21:00:11浏览次数:42  
标签:折半 一半 复杂度 笔记 leq 搜索 dfs

关于算法

折半搜索,又称 meet in the middle 算法。
顾名思义,就是将整个搜索的过程分成两个部分分别进行搜索,然后再将两个部分搜索出来的答案进行合并,得到最终的答案。

dfs 搜索算法一般都是指数级别的,那么我们假如每次 dfs 时都有两种决策,那么我们执行 dfs 算法的时间复杂度为 \(O(2^n)\),如果 \(n\) 再稍微大一点时,就会导致超时,那么这个时候我们就会采取折半搜索。

我们来看一下下面两张图。
1
很容易发现,左边这一张图是普通的只执行 \(1\) 次的搜索树,而右边这张图则是从两个不同的点分别出发搜索产生的搜索树。

显然,左边这一个搜索树随着层数的增加,每一层的节点数成指数级增长,最终会在最后一层的某一个节点上产生答案。(假设黄色部分表示最终答案。)
而右边的折半搜索产生的搜索树,恰巧就可以避免随着层数的不断增加导致节点数大规模增长,当第二次搜索完毕之后,和第一次搜索出来的结果进行合并操作,得到最终的答案。

那么这个时候我们的折半搜索的时间复杂度就会优化为 \(O(2^{n/2})\),再加上我们合并答案的复杂度。可见相比普通的 dfs 搜索,折半搜索起到了很大的优化作用。

例题

[CEOI2015 Day2] 世界冰球锦标赛

这道题乍一看可以用背包做,但 \(1 \leq M \leq 10^{18}\),\(dp\) 数组开不了这么打,显然只能用搜索来解决。
但, \(1 \leq N \leq 40\),对于普通搜索来讲 \(O(2^n)\) 的时间复杂度还是跑不了这么大的数据,那么我们考虑折半搜索。

以 \(N \div 2\) 为界限,先搜索前面一半,并且把前面一半的数据打表储存下来(这里储存的不是方案数,而是每种方案所花的钱,原因看下面合并),再搜索后面一半,搜索完毕后与前一半合并,记得得到最终答案。

对于折半搜索来讲,难点一般都在如何合并上,对于这一道题,显然,当我们后一半搜索完毕后,我们可以对前一半的所有结果(前一半每种方案所花的钱)进行从小到大排序,再二分查找,我们看前一半中的方案所花的钱,加上当前花的钱,超不超过 \(M\),若不超过,则当钱花的钱可以和前一半所有合法的数,组成一种方案。

代码实现:

#include <bits/stdc++.h>
#define int long long
const int N = 105;
using namespace std;
inline int read(){
	int r = 0,w = 1;
	char c = getchar();
	while (c < '0' || c > '9'){
		if (c == '-'){
			w = -1;
		}
		c = getchar();
	}
	while (c >= '0' && c <= '9'){
		r = (r << 3) + (r << 1) + (c ^ 48);
		c = getchar();
	}
	return r * w;
}
int n,m,arr[N],half,cnt = 0,v[1 << 24];
int ans = 0;
void dfs1(int pos,int s){
	if (pos == half + 1){
		cnt++;
		v[cnt] = s;
		return;
	}
	if (1ll * s + 1ll * arr[pos] <= m){
		dfs1(pos + 1,1ll * s + 1ll * arr[pos]);
	}
	dfs1(pos + 1,s);
}
void dfs2(int pos,int s){
	if (pos == n + 1){ // 合并。
		ans += upper_bound(v + 1,v + cnt + 1,m - s) - v - 1; // 二分查找。
		return;
	}
	if (1ll * s + 1ll * arr[pos] <= m){
		dfs2(pos + 1,1ll * s + 1ll * arr[pos]);
	}
	dfs2(pos + 1,s);
}
signed main(){
	n = read(),m = read();
	for (int i = 1;i <= n;i++){
		arr[i] = read();
	}
	half = n >> 1;
	dfs1(1,0);
	sort(v + 1,v + cnt + 1);
	dfs2(half + 1,0);
	printf("%lld",ans);
	return 0;
}  

更多练习:
CF1006F Xor-Paths
CF888E Maximum Subsequence
AcWing 171.送礼物

标签:折半,一半,复杂度,笔记,leq,搜索,dfs
From: https://www.cnblogs.com/WiuehPlus/p/17674196.html

相关文章

  • 【学习笔记】二分图基础
    二分图与网络流基础(网络流待学)查看目录目录前置知识:二分图:二分图的定义:二分图的判定:例题:[NOIP2010提高组]关押罪犯二分图的匹配:匈牙利算法:例题:[ABC317G]Rearranging[ABC317G]Rearranging前置知识:tarjan强连通分量:有向图中几个点可以相互到达,就称这几个点是强连通......
  • 莫队学习笔记(如何处理增量)
    题目传送门:序列考虑我们已经求出了区间\([l,r]\)的答案,现在要求\([l,r+1]\)的答案。很明显增多的子序列有\((l,r+1),(l+1,r+1)...(r+1,r+1)\)。考虑求出\([l,r+1]\)中的最小值的位置\(p\)(可以用\(rmq~O(1)\)求出),那么\(a_p\)的贡献就是\(a_p\times(p-l+1)\),现在我......
  • 《管理学》阅读笔记(3)
    管理的本质‌‌‌‌管理的本质从某种意义上说是对组织成员在活动中的行为进行协调组织成员的行为能够被有效协调的前提是他们愿意接受这种协调,而且他们的行为具有一定程度的可协调性。管理是对人或对人的行为的管理;‌‌‌‌管理者的主要工作是选择对的人去做对的事情......
  • 学习笔记1-指令级并行
    指令级并行1.概念1.1.指令级并行(ILP)有两种实现方法:(1)依靠硬件来动态发现并实现并行;(2)依靠软件技术在编译时静态发现并行。1.2.数据依赖与冒险数据依赖(三种类型):数据依赖、名称依赖和控制依赖。1.数据依赖:1)指令i生成的结果可能会被指令j用到。2)指令j数据依赖于指令k,......
  • Leetcode刷题笔记——二分法
    二分法是搜索算法中极其典型的方法,其要求输入序列有序并可随机访问。算法思想为输入:有序数组nums,目的数值target要求输出:如果target存在在数组中,则输出其index,否则输出-1将原数组通过[left,right]两个索引划分范围,初值left=0,right=数组的最后一个元素当left<=right时mid......
  • 旧笔记本秒变web服务器---nat123 一款优秀的内网穿透服务器
    2014买的第一台笔记本,win7系统,加过内存,重装过多次系统但是无法运行win10,用来开发已经相当吃力,但运行还是比较流畅的,扔掉可惜,卖二手也卖不了多少,后来经过多次的思考与尝试,将厚重的光驱位扩展了500G硬盘,安装了winNAS,将其改装成了私有NAS网盘,但是客户端只有手机端app,对于我这做web开......
  • [学习笔记] 莫队
    一、普通莫队莫队是一种基于分块的算法,由莫队提出(orz)。莫队可以解决一类查询序列区间信息的题。可以使用该算法的前提是维护的信息必须可以在\(O(1)\)(如果用map/set可以带\(\log\),或者优化成\(O(1)\))内将\([l,r]\)的答案扩展到\([l-1,r],[l+1,r],[l,r-......
  • windows10,编译rust程序到so文件,供android调用,笔记
    1、用D:\myProgram\android_sdk\ndk\ndk-22.0.7026061\ndk-build.cmd编译,全路径,只写ndk-build,似乎不行2、在androidas里编译,提示soisnotaABI,其实是so放错地方了。应该放在src\main\jniLibs\arm64-v8a目录下(其他cpu类似),我就是缺少arm64-v8a目录,导致这个错误,新建arm64-v8......
  • 『学习笔记』狄利克雷生成函数
    定义一般地,对于一个函数\(f\),定义它的狄利克雷生成函数(简写为DGF)为:\[\tilde{F}(x)=\sum_{i\ge1}^\infty\dfrac{f_i}{i^x}.\]即:\[\tilde{F}(x)=f_1+\dfrac{f_2}{i^2}+\dfrac{f_3}{i^3}+\dfrac{f_4}{i^4}+\cdots.①\]性质若\(f\)是积性函数,则一定满足:......
  • 《C++并发编程实战》读书笔记(1):线程管控
    1、线程的基本管控包含头文件<thread>后,通过构建std::thread对象启动线程,任何可调用类型都适用于std::thread。voiddo_some_work();structBackgroundTask{voidoperator()()const;};//空的thread对象,不接管任何线程函数std::threadt1;//传入普通函数std::thr......