集训2023/03/19+03/26:搜索优化
一、剪枝
(理论上)非常简单,就是在搜索时直接排除不可能出现或不是最优的情况,从而提高程序执行效率。
1.1 拼接正方形
是搜索无疑了,先从简单的想起。
记
如果用朴素搜索的话,就是枚举每一根木棒在哪一条边里,时间复杂度有
于是考虑剪枝。由于题目的要求是拼接正方形,所以每一条边的长度都会等于
1.2 P1559 运动员最佳匹配问题
首先想到暴力枚举每一个男的配哪一个女的,显然超时。
考虑优化。假设有两男
- 如果
V_1 \ge V_2 ,则排除(a, d), (b, c) ; - 否则排除
(a, c), (b, d) 。
至于怎么排除所有应该排除的配对:两两枚举,反正
二、双端队列广搜
适用于有部分优先级的问题,比如说优先搜索带有某种属性的元素。
2.1 P4667 [BalticOI 2011 Day1]Switch the Lamp On
如果一组顶点直接有电线直连,则设这两个点之间的边权为
但是普通广搜无法解决这个问题,因为它要求无边权。
思考普通广搜为什么要求无边权。我们发现,这是为了保证搜到每一个点时都是目前的最短路状态,从而保证算法的正确性。
而现在我们只有两种情况,就可以用双端队列。
三、双向搜索
3.1 最接近目标值的子序列和
暴搜
于是我们想到,可以分别搜出前一半和后一半的结果,再把他们总结起来,得到整个问题的结果,思路有点像分治。
具体的实现方法,就是先把两边都搜完,最后拿个双指针扫一遍找最接近
3.2 迷宫最短路
双向广搜。
就像多起点一样,开始时把起点和终点都加入队列。
由于一个是从起点搜,一个是从终点搜,那么如果两边搜到了同一个点,就代表着找到最短路了。由于两端都在做正常广搜,正确性显然。
注意特判起点与终点重合的情况。
3.3 加快速度的原理
双向广搜,为什么可以加快程序速度?
我们其实可以把普通广搜想象成从起点不断增加半径往外画圆,直到画到终点为止:
而双向广搜,就是从起点和终点同时画圆,直到两边的圆出现交点为止:
这样,就只需要搜一半的距离(或者说
四、启发式搜索
不影响算法正确性的剪枝就是启发式剪枝,用了启发式剪枝的搜索就是启发式搜索。
4.1 启发式剪枝 - P1120 小木棍
全排列枚举,但是加了优化。
- 原始的木棍由于长度相等,每根的长度都是长度之和的因数;
- 先选长的木棍,因为短木棍组合更灵活,而长木棍更多是“凑”长度的作用;
- 避免重复,令组中木棍长度单调递减,所有组的第一根木棍长度单调递减;
- 如果选了一根木棍,发现不行,那么就不要再选长度相等的木棍了,浪费时间;
- 如果凑数时剩余的长度刚好对应一根木棍,那就选它了,因为与其用几根较小的不如用长点的木棍,更灵活。
4.2 A*
如果是我们找最短路之类的枚举(至少暴力方法是枚举)问题时,我们会优先枚举更像是答案(或者看起来更像)的情况。
可惜计算机并不知道什么叫做“更像答案”,所以计算机干不了这事。
但是,如果我们能让计算机理解“更像答案”是什么,我们是不是就可以让计算机在处理问题的时候像我们一样“思考”了呢?
是的,我们确实可以。
我们建立一个函数 注意不是咕值),
令
比如说,如果
4.3 A* 的正确性
广搜的正确性是因为每次到达一个点时,如果存在更短的路线那就不会再次搜到了。但是,A* 的估值函数已经把广搜的正确性破坏的一点不剩。
令
于是有
因为我们会向
由于
综上,只要满足
4.4 A* - 迷宫最短路
将
五、迭代加深
我们的两种搜索方法(深搜与广搜),是各有优缺点的:
| 深搜 | 广搜 | |
|---|---|---|
| 时间 | 明显慢,因为要搜到底 | 快,因为是“广度” |
| 空间 | 少,只存当前状态 | 明显多,要存搜到的每个状态 |
一般来说,那些没有“深度”的定义(或者很模糊)的问题,是不能用深搜解决的(因为会一直搜下去最后死递归)。但是,如果题目把广搜也给卡掉了(卡空间),那我们难道就做不了了吗?
显然是错误的,迭代加深搜索就专门解决这种问题。
我们人为限定一个深度
复杂度为
5.1 P1763 埃及分数
枚举所需要的分数个数,然后往下搜。
有一个小优化,就是在往下搜分母的时候让它单调递减。