入门教程 高级搜索

高级搜索

信息

题解进度

Solved # Title Editorial
Solved A 【ICPC 2004 上海站】 【题解】The Rotation Game
B 【ICPC 2006 横滨站 日本】
C 【SCOI 2005 四川】
D 【HDU 2006 校赛】
E 【ICPC 2001 坎普尔站 印度】
F 【ICPC 2001 大田站 韩国】
G 【ICPC 1997 乌尔姆站 德国】
Solved H 【ICPC 1996 乌尔姆站 德国】通常搜索 双向BFS 【题解】Knight Moves
Solved I 【HDU 2011 校级赛】奇怪搜索 双向BFS 【题解】Nightmare Ⅱ
Solved J 【ICPC 1998 SCUSA站】八数码 IDA* 【题解】Eight
Solved K 【HDU 2017 暑期多校】次短路 A* 【题解】Two Paths
Solved L 【ICPC 2017 沈阳站 网络赛】K短路 A* 【题解】Made In Heaven
Solved M 【CCPC 2019 网络赛】不定点K短路 想法题 【题解】path

通知栏

3月30-4月5日这周的新生训练准备训练高级搜索,就是讲双向广度搜索,迭代加深、A*算法,IDA*,你能负责安排吗?php

第七周 高级搜索

1、视频:

1)搜索相关:

https://github.com/luoyongjun999/code/tree/master/%E8%A1%A5%E5%85%85%E8%B5%84%E6%96%99

2)A*:

https://www.bilibili.com/video/BV1D4411X71L

3)IDA*:
https://www.bilibili.com/video/BV1K4411R78Y

// 看前面一段简介就差很少了

// 可能去网上看博客会有更多理解

2、专题训练赛:

https://vjudge.net/contest/363902

// 可能会考虑陆续更一波题解

3、综合训练赛:
https://vjudge.net/contest/363899

双向广度搜索 (Bidirectional BFS)

这个实际上是BFS的优化技巧,能够证实,从两个起点开始BFS,比单点开始要优化不少。html

全部BFS过的题,你均可以写成双向的(首先看题目时间卡不卡,其次看实现麻不麻烦)。git

  • https://www.geeksforgeeks.org/bidirectional-search/

伪代码

BFS (queue_$){
        q.swap(queue)          //一个关键
        while (!q.empty()) {
            从q取出队首点
            
            if 遇到另外一个点集:
            	return 有解,距离
            
            for () {
                 dr := r + d
                 dc := c + d
                判断合法性
                新点插入 queue_$ //另外一个关键
       			更新标记数组,vis,dist
            }
        }
        return 无解
    };

	BiBFS(){
        res 
        queue_s	
        queue_t	
        分别插入对应的起点
        更新标记数组,vis,dist
            
        while 二者不都为空 :
        	// 这里有点启发式的思想,每次更新点集更小的队列
            if s的状态点数多于t,且queue_t非空 :
                res = BFS(queue_t);
            else :
                res = BFS(queue_s);
        
            if (有解且相遇) return res;
        }
        return 无解
    };

BBFS相关习题


【题解】Knight Moves

hdu 1372 - Knight Movesgithub

题意:web

就问你从棋盘上一个位置到另一个位置的最短路。算法

思路:数组

我以为你只要看懂了上面的伪代码的话,应该就能写了。网络

甚至若是你厉害的话,普通搜索也能过(不肯定app

[个人代码 1372 BBFS.cpp](https://github.com/TieWay59/HappyACEveryday/blob/master/2020codes/hdu/1372 BBFS.cpp)框架


【题解】Nightmare Ⅱ

hdu 3085 - Nightmare Ⅱ

模型是方格地图,有两我的M和G要见面,每一个人只能四向移动。其中M能够一单位时间移动三次,G能够一单位时间移动一次。图上一开始有两个Z,这个Z每一个单位时间会分裂,占满曼哈顿距离为2的全部位置。Z能够覆盖X,可是MG不能够走到XZ。每一时刻M或G移动前,看做Z已经分裂完毕。注意,题意隐含:MG被Z覆盖也会没法行动。

思路:这个提示也不算传统的宽搜,不是很入门,可能还有点入坟。

  • 一开始我觉得,M的移动是能够肯定的,因而把它的移动设成一个dir数组来判。后来才意识到,M移动的过程当中三次位移每一次都不能落在Z上,那么若是是有个第二次落脚的位置恰变成Z了,那么后续的第三个位置是M达不到的。这告诉你,这个题很是的有鬼。
  • 因为G和Z都是单位时间变化的,M又有移动的特殊性,总体作法仍是以双向宽搜为主,可是,M的每次移动都要单独拿出来判,具体地说就是要有三次while(!queue.empty())给M点判移动。
  • 而后,因为人必须是同一个单位时间内相遇,并且每一个时间人都必须移动,因此这不是常规的最短路问题,而是枚举单位时间,更新全部上个时刻的位置,到下个位置,若是能够到达,若是在这个单位时间内,M首次走到G通过的位置(反之等价),能够说这就是相遇的最少时间。

个人代码 3085 Bidirectional BFS.cpp

迭代加深 ID(Iterative Deepening), IDDFS

这个算法代码上只是DFS稍微添加点东西而已。

迭代加深搜索,实际上就是作不少次深搜,同时逐渐提升每次深搜的深度限制。直到找到答案就结束。

能够证实,这样作能够在时间上接近BFS,在空间上接近DFS,能够避免两种基本搜索算法的极端。

在目标不深,分支不少的状况下,运行表现比较好。

A* (A_star)

这个算法的思想其实也不复杂,可是代码细节多点。

A星算法的启发性在于规定一个估价函数,来猜测一个点到目标的后续花费。

能够是不许的猜测,好比在方格模型中,用曼哈顿距离计算。

在代码上的表现,经常和bfs,反向图,最短路有关。

因而**“后继节点”就能够经过“已有花费+可能的后续花费”**的某个函数来比较。

特别的是,这个算法不太用在算法竞赛,而在游戏开发中更有做用。

第K短路问题

就是求某个点到某个点的第k短的路径长度。(咱们所说的最短路通常就是K=1的状况)

放在这里讲也是由于有一种Astar的简单作法。

Astar作法的K短路不算太难,每一步都很清晰:

  1. 首先创建反向图(就是全部边和你原来的图相反的图,无向图就免去这一步)
  2. 而后在反向图从终点开始作一遍单源最短路(能够时dijkstra)
  3. 从起点开始作优先队列优化的BFS。
  4. Astar的体如今于优先队列中的节点的优先级和用启发函数要有关。
  5. 也就是按照f(u)=dis_start(u)+dis_final(u)从小到达排序。
  6. 当第K次遇到终点的时候,当前的距离就是K短路。

是否是看起来很暴力?确实很暴力,记住这样作的最坏复杂度是 O ( K N l o g ( N ) ) O(KNlog(N)) 的。

  • https://oi-wiki.org/graph/kth-path/
  • 世界上有很低复杂度的科技 O ( N l o g ( N ) + M l o g ( M ) + K l o g ( K ) ) O(Nlog(N)+Mlog(M)+Klog(K)) ,可是代码很长很长,而且很难理解:https://www.isi.edu/natural-language/people/epp-cs562.pdf https://www.isi.edu/natural-language/people/epp-cs562.pdf

K短路相关习题


【题解】Two Paths

hdu 6181- Two Paths

题意:

这个题是给你无向图要你求次短路,也就是K=2的状况。

比较通常吧,并且也有别的作法(改写dijkstra便可)

当心处理爆int的问题。

[个人代码 6181 K短路(边权ll).cpp](https://github.com/TieWay59/HappyACEveryday/blob/8e937dc620db56668f565d1e38a337e40a6f4903/2020codes/hdu/6181 K短路(边权ll).cpp)


【题解】Made In Heaven

计蒜客 A1992 - Made In Heaven

题意:

这是通常的询问K短路,这个题比较重要,是沈阳网络赛的题目。

给你们唠叨两句,咱们集训队暑期队伍排名,都会参照网络赛比赛的排名的。

这个题看起来好像不能用普通的Astar作,实际上他题目给你了一个长度剪枝,要充份利用。

[个人代码 A1992 K短路 Astart 剪枝.cpp](https://github.com/TieWay59/HappyACEveryday/blob/8e937dc620db56668f565d1e38a337e40a6f4903/2020codes/jisuanke/A1992 K短路 Astart 剪枝.cpp)


【题解】path

hdu 6705 - path

题意:

有向图模型,q次询问整个图上任意源汇的第K短路。

q次询问不少,你固然要预处理出前max(k)的答案。

思路:

枚举全部路径是不可能的,咱们指望的是,在最少的枚举次数下,枚举到尽可能短的路径。

对于一条当前剩下路径中最短的路径 P c u r P_{cur} ,设这个路径最后一条边为 P c u r : E ( u , i ) P_{cur}:E(u,i) ,表示从 u u 出发的第 i i 短的边。

那么根据这条路径,下一条比这个路径长的路径可能有(假设存在):

  • P : E ( u , i + 1 ) P:E(u,i+1)
  • P c u r + E ( t o ( u , i ) , 0 ) P_{cur} + E(to(u,i),0)

先不去提这二者怎么比较,以及存在性的问题。

假如咱们每次拿到一条当前的最短路径,而后放回这两种可能的后继,是否是总能保证工做集合的完备,而且这个集合的体积不会很大。

因而能够总结出,路径节点的表示(长度,u,i ),以及转移过程。

剩下的问题是,初始状况应该是怎么样的。

咱们要保证初始的最短路径的枚举集完备,而且后续路径不会重复。

答:就是全部点的出发的最短边的集合。(本身思考为何)

具体的实现只须要会优先队列就能够写了,没有什么固定的章法。

[个人代码 6705 任意源汇K短路 想法.cpp](https://github.com/TieWay59/HappyACEveryday/blob/a13239ffeaaf5a3252e3aabdec1b158ad25f1674/2020codes/hdu/6705 任意源汇K短路 想法.cpp)

IDA* (Iterative deepening A*)

顾名思义,就是把上面连个算法结合起来的算法。

  • https://blog.csdn.net/xiaonanxinyi/article/details/97896085?depth_1-utm_source=distribute.pc_relevant_right.none-task&utm_source=distribute.pc_relevant_right.none-task
  • 不存在的:wiki

IDA*相关习题


【题解】The Rotation Game

hdu 1667 - The Rotation Game

题意:

给你一个滚动游戏的模型,8方向能够拉动数组循环滚动1格。问你把中间八个移动到成同一个数的状态的最短移动方案,若是有多解要输出字典序最小的解。

第二行要输出最终格局中间八个数是哪种。

思路:

首先咱们来看怎么转化成图论模型,也就是模拟的部分。

你能够把这个#每一行从左到右,而后从上到下,这样编号,一个格局就变成一个长度为24的数组,每一个元素都属于[1,2,3]

数组变成简单结点的方法有不少,主要就是状压或者哈希。

我采用了哈希成一个4进制的大整数的作法。(实际上你能够优化成二进制的状压,给你们思考)

state = 0;
for (auto in:input)
    if ('0' <= in && in < '4')
        state = (state << 2) + int(in - '0');

而后状态怎么转移。你想每一次拉动数组,全部元素都朝着一个方向位移,首部的元素放到末尾。这个过程其实能够等价于,把第一个元素冒泡交换到最后一个。

因此你须要的是实现一个子操做,交换。根据上面的编号和状压的方式,这个函数不难实现。

const auto digitSwap = [](ll &s, ll a, ll b) {
        a = 2 * (a - 1);
        b = 2 * (b - 1);
        ll x = (s >> a) & 3;
        ll y = (s >> b) & 3;
        s = s - (x << a) - (y << b)
            + (x << b) + (y << a);
    };

而后就是终点状态的表示。在这个问题中,个人思路是枚举最终中间的数字是什么(也就全1或者2,3)而后把其余数组位置填上0,哈希成一个终点状态,每次当前状态都跟这个节点比较便可。

有了以上的理解,你应该能够写出一个爆搜的作法了,可是这样会TLE,或者MLE。

怎么优化呢?

  • 考虑迭代加深,枚举深度去DFS,每次深度+1保证第一次遇到的答案是最短的。
  • 考虑一个启发(也就是启发函数h)你能够假设,中间八个位置,有几个和目标状态不同,就是最少还差几步(固然看起来不必定,但这个考虑不会使得方案变差)这样你就能肯定一个状态是否有可能在你枚举的步长内达到终点。

有了这两点优化,就是标准的IDA*的模样了。

稍微顺带一提,个人状态是用4进制表示的,其实你也能够经过每次修改起始状态优化成二进制。由于你想,若是你的最终状态中心是1,那么2和3的位置实际上是对你搜索的过程没有意义的,均可以当作是0。这样能够进一步优化。

个人代码 1667 IDAstar.cpp


【题解】Eight

hdu 1043 - Eight

八数码问题实际上是一个老掉牙的麻烦题,思惟障碍主要有如下几点:

  • 无解的结论:能够证实,输入的数组(除了x之外)逆序对有奇数个,那么这个八数码是无解的。
  • 启发式估价函数:用每一个数字(除了x),与目标位置的曼哈顿距离,求和来表示预期的代价。
  • 其余细节:对于有解的状况,移动步数不该该不少(不会上千),可是同一步数的方案可能会不少。
  • 采起搜索方案:BFS空间危险,DFS深度危险,那么就用迭代加深吧,好像还有估计函数,那就是IDA*了。

若是你已经懂了以上的思考点,而且了解了IDA*算法的框架,就能够开始敲了。

其实你不用把IDA*想的太复杂,也不须要去找模板,思路就是:你枚举限制DFS深度屡次去DFS,中间用启发式估价函数剪枝就行了。这个题个人剪枝是:steps+h(state)>depth,这表明估计到预期的步数超过限制深度。

[个人代码 1043 IDAstar2.cpp](https://github.com/TieWay59/HappyACEveryday/blob/master/2020codes/hdu/1043 IDAstar2.cpp)

我还去研究了洛谷的另一个八数码的题不输出方案,只要输出最小步数的。这个题的数据是卡A*的作法的。你看,这个算法多么尴尬,在必定状况下还很容易被卡掉。

话题以外

我来解释一下为何要有这么复杂的算法。

以及为何例题都那么——陈旧。

你能够先浏览一下这篇文章:A*,Dijkstra,BFS算法性能比较及A*算法的应用

上面这些花里胡哨,看似高级的搜索算法,仍是在解决一个老掉牙的问题,最短路。

这里的“最短路”,不必定是图形上的最短路径,还多是某个事物(首当其冲,滑块拼图)的最少次数之类的。

可是像Dijkstra在稠密的图,或者方格图上的表现,会逊色不少;还有一些抽象状况(滑块拼图)分支庞杂,没法建图。因此会存在像IDA*针对上述这样的状况更有效的算法。

可是,也是由于针对性太特殊了,致使这样的算法变不出太多的花样。不是说出不了难题,是很难弄出好题。并且一样的题可能用其余歪门邪道的方法也能够作出来,好比dfs巧妙剪枝,时间复杂度很难卡出那么多不一样的优化的作法。

因此近五年不多有出这样的题,历史上也很难找到相似的题。

但做为准备者,我不敢保证说之后就不会有了,并且极可能这样的题目再次现身,会是以很难的题目出现的。(由于简单的基础题你们刷的愈来愈多了)

看起来,现代的搜索题,更多在花样上创新,而不会在老花样上吊胃口了。

点击查看源网页