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
这个实际上是BFS的优化技巧,能够证实,从两个起点开始BFS,比单点开始要优化不少。html
全部BFS过的题,你均可以写成双向的(首先看题目时间卡不卡,其次看实现麻不麻烦)。git
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 无解 };
hdu 1372 - Knight Movesgithub
题意:web
就问你从棋盘上一个位置到另一个位置的最短路。算法
思路:数组
我以为你只要看懂了上面的伪代码的话,应该就能写了。网络
甚至若是你厉害的话,普通搜索也能过(不肯定app
[个人代码 1372 BBFS.cpp](https://github.com/TieWay59/HappyACEveryday/blob/master/2020codes/hdu/1372 BBFS.cpp)框架
模型是方格地图,有两我的M和G要见面,每一个人只能四向移动。其中M能够一单位时间移动三次,G能够一单位时间移动一次。图上一开始有两个Z,这个Z每一个单位时间会分裂,占满曼哈顿距离为2的全部位置。Z能够覆盖X,可是MG不能够走到XZ。每一时刻M或G移动前,看做Z已经分裂完毕。注意,题意隐含:MG被Z覆盖也会没法行动。
思路:这个提示也不算传统的宽搜,不是很入门,可能还有点入坟。
while(!queue.empty())
给M点判移动。个人代码 3085 Bidirectional BFS.cpp
这个算法代码上只是DFS稍微添加点东西而已。
迭代加深搜索,实际上就是作不少次深搜,同时逐渐提升每次深搜的深度限制。直到找到答案就结束。
能够证实,这样作能够在时间上接近BFS,在空间上接近DFS,能够避免两种基本搜索算法的极端。
在目标不深,分支不少的状况下,运行表现比较好。
这个算法的思想其实也不复杂,可是代码细节多点。
A星算法的启发性在于规定一个估价函数,来猜测一个点到目标的后续花费。
能够是不许的猜测,好比在方格模型中,用曼哈顿距离计算。
在代码上的表现,经常和bfs,反向图,最短路有关。
因而**“后继节点”就能够经过“已有花费+可能的后续花费”**的某个函数来比较。
特别的是,这个算法不太用在算法竞赛,而在游戏开发中更有做用。
就是求某个点到某个点的第k短的路径长度。(咱们所说的最短路通常就是K=1的状况)
放在这里讲也是由于有一种Astar的简单作法。
Astar作法的K短路不算太难,每一步都很清晰:
f(u)=dis_start(u)+dis_final(u)
从小到达排序。是否是看起来很暴力?确实很暴力,记住这样作的最坏复杂度是 的。
题意:
这个题是给你无向图要你求次短路,也就是K=2的状况。
比较通常吧,并且也有别的作法(改写dijkstra便可)
当心处理爆int的问题。
[个人代码 6181 K短路(边权ll).cpp](https://github.com/TieWay59/HappyACEveryday/blob/8e937dc620db56668f565d1e38a337e40a6f4903/2020codes/hdu/6181 K短路(边权ll).cpp)
题意:
这是通常的询问K短路,这个题比较重要,是沈阳网络赛的题目。
给你们唠叨两句,咱们集训队暑期队伍排名,都会参照网络赛比赛的排名的。
这个题看起来好像不能用普通的Astar作,实际上他题目给你了一个长度剪枝,要充份利用。
[个人代码 A1992 K短路 Astart 剪枝.cpp](https://github.com/TieWay59/HappyACEveryday/blob/8e937dc620db56668f565d1e38a337e40a6f4903/2020codes/jisuanke/A1992 K短路 Astart 剪枝.cpp)
题意:
有向图模型,q次询问整个图上任意源汇的第K短路。
q次询问不少,你固然要预处理出前max(k)的答案。
思路:
枚举全部路径是不可能的,咱们指望的是,在最少的枚举次数下,枚举到尽可能短的路径。
对于一条当前剩下路径中最短的路径 ,设这个路径最后一条边为 ,表示从 出发的第 短的边。
那么根据这条路径,下一条比这个路径长的路径可能有(假设存在):
先不去提这二者怎么比较,以及存在性的问题。
假如咱们每次拿到一条当前的最短路径,而后放回这两种可能的后继,是否是总能保证工做集合的完备,而且这个集合的体积不会很大。
因而能够总结出,路径节点的表示(长度,u,i ),以及转移过程。
剩下的问题是,初始状况应该是怎么样的。
咱们要保证初始的最短路径的枚举集完备,而且后续路径不会重复。
答:就是全部点的出发的最短边的集合。(本身思考为何)
具体的实现只须要会优先队列就能够写了,没有什么固定的章法。
[个人代码 6705 任意源汇K短路 想法.cpp](https://github.com/TieWay59/HappyACEveryday/blob/a13239ffeaaf5a3252e3aabdec1b158ad25f1674/2020codes/hdu/6705 任意源汇K短路 想法.cpp)
顾名思义,就是把上面连个算法结合起来的算法。
【ICPC 2004 上海站】hdu 1667 - The Rotation Game
【ICPC 2006 横滨站 日本】uva 1374 - Power Calculus
【HDU 2006 校赛】 hdu 1560 - DNA sequence
【SCOI 2005 四川】luogu P2324 - 骑士精神 link
【ICPC 2001 坎普尔站 印度】uva 11212 - Editing a Book 源地址
【ICPC 2001 大田站 韩国】uva 1603 - Square Destroyer
【ICPC 1997 乌尔姆站 德国】uva 529 - Addition Chains 源地址
【ICPC 1998 SCUSA站】hdu 1043 - Eight 也就是人人都会的八数码啦。
题意:
给你一个滚动游戏的模型,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。
怎么优化呢?
有了这两点优化,就是标准的IDA*的模样了。
稍微顺带一提,个人状态是用4进制表示的,其实你也能够经过每次修改起始状态优化成二进制。由于你想,若是你的最终状态中心是1,那么2和3的位置实际上是对你搜索的过程没有意义的,均可以当作是0。这样能够进一步优化。
八数码问题实际上是一个老掉牙的麻烦题,思惟障碍主要有如下几点:
若是你已经懂了以上的思考点,而且了解了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巧妙剪枝,时间复杂度很难卡出那么多不一样的优化的作法。
因此近五年不多有出这样的题,历史上也很难找到相似的题。
但做为准备者,我不敢保证说之后就不会有了,并且极可能这样的题目再次现身,会是以很难的题目出现的。(由于简单的基础题你们刷的愈来愈多了)
看起来,现代的搜索题,更多在花样上创新,而不会在老花样上吊胃口了。