高級搜索
文章目錄
信息
題解進度
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*,你能負責安排嗎?
第七週 高級搜索
一、視頻:
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
// 看前面一段簡介就差不多了
// 可能去網上看博客會有更多理解
二、專題訓練賽:
https://vjudge.net/contest/363902
// 可能會考慮陸續更一波題解
三、綜合訓練賽:
https://vjudge.net/contest/363899
雙向廣度搜索 (Bidirectional BFS)
這個其實是BFS的優化技巧,可以證明,從兩個起點開始BFS,比單點開始要優化很多。
所有BFS過的題,你都可以寫成雙向的(首先看題目時間卡不卡,其次看實現麻不麻煩)。
- 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相關習題
- 【ICPC 1996 烏爾姆站 德國】hdu 1372 - Knight Moves
- 【HDU 2011 校級賽】hdu 3085 - Nightmare Ⅱ
【題解】Knight Moves
題意:
就問你從棋盤上一個位置到另外一個位置的最短路。
思路:
我覺得你只要看懂了上面的僞代碼的話,應該就能寫了。
甚至如果你厲害的話,普通搜索也能過(不確定
[我的代碼 1372 BBFS.cpp](https://github.com/TieWay59/HappyACEveryday/blob/master/2020codes/hdu/1372 BBFS.cpp)
【題解】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,可以避免兩種基本搜索算法的極端。
在目標不深,分支很多的情況下,運行表現比較好。
- jianshu
- geeksforgeeks
- 不存在的:wiki
A* (A_star)
這個算法的思想其實也不復雜,但是代碼細節多點。
A星算法的啓發性在於規定一個估價函數,來猜想一個點到目標的後續花費。
可以是不準的猜想,比如在方格模型中,用曼哈頓距離計算。
在代碼上的表現,常常和bfs,反向圖,最短路有關。
於是**“後繼節點”就可以通過“已有花費+可能的後續花費”**的某個函數來比較。
特別的是,這個算法不太用在算法競賽,而在遊戲開發中更有作用。
第K短路問題
就是求某個點到某個點的第k短的路徑長度。(我們所說的最短路一般就是K=1的情況)
放在這裏講也是因爲有一種Astar的簡單做法。
Astar做法的K短路不算太難,每一步都很清晰:
- 首先建立反向圖(就是所有邊和你原來的圖相反的圖,無向圖就免去這一步)
- 然後在反向圖從終點開始做一遍單源最短路(可以時dijkstra)
- 從起點開始做優先隊列優化的BFS。
- Astar的體現在於優先隊列中的節點的優先級和用啓發函數要有關。
- 也就是按照
f(u)=dis_start(u)+dis_final(u)
從小到達排序。 - 當第K次遇到終點的時候,當前的距離就是K短路。
是不是看起來很暴力?確實很暴力,記住這樣做的最壞複雜度是的。
- https://oi-wiki.org/graph/kth-path/
- 世界上有很低複雜度的科技,但是代碼很長很長,並且很難理解:https://www.isi.edu/natural-language/people/epp-cs562.pdf https://www.isi.edu/natural-language/people/epp-cs562.pdf
K短路相關習題
- 【HDU 2017 暑期多校】hdu 6181- Two Paths 也就是人人都會的次短路。
- 【ICPC 2017 瀋陽站 網絡賽】 計蒜客 A1992 - Made In Heaven 也就是人人都會的K短路。
- 【CCPC 2019 網絡賽】hdu 6705 - path 不定點K短路 想法題
【題解】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
題意:
這是一般的詢問K短路,這個題比較重要,是瀋陽網絡賽的題目。
給大家嘮叨兩句,我們集訓隊暑期隊伍排名,都會參照網絡賽比賽的排名的。
這個題看起來好像不能用普通的Astar做,實際上他題目給你了一個長度剪枝,要充份利用。
[我的代碼 A1992 K短路 Astart 剪枝.cpp](https://github.com/TieWay59/HappyACEveryday/blob/8e937dc620db56668f565d1e38a337e40a6f4903/2020codes/jisuanke/A1992 K短路 Astart 剪枝.cpp)
【題解】path
題意:
有向圖模型,q次詢問整個圖上任意源匯的第K短路。
q次詢問很多,你當然要預處理出前max(k)的答案。
思路:
枚舉所有路徑是不可能的,我們期望的是,在最少的枚舉次數下,枚舉到儘量短的路徑。
對於一條當前剩下路徑中最短的路徑,設這個路徑最後一條邊爲 ,表示從出發的第短的邊。
那麼根據這條路徑,下一條比這個路徑長的路徑可能有(假設存在):
先不去提這兩者怎麼比較,以及存在性的問題。
假如我們每次拿到一條當前的最短路徑,然後放回這兩種可能的後繼,是不是總能保證工作集合的完備,並且這個集合的體積不會很大。
於是可以總結出,路徑節點的表示(長度,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*相關習題
-
【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 也就是人人都會的八數碼啦。
【題解】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。這樣可以進一步優化。
【題解】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巧妙剪枝,時間複雜度很難卡出那麼多不同的優化的做法。
所以近五年很少有出這樣的題,歷史上也很難找到類似的題。
但作爲準備者,我不敢保證說以後就不會有了,而且很可能這樣的題目再次現身,會是以很難的題目出現的。(因爲簡單的基礎題大家刷的越來越多了)
看起來,現代的搜索題,更多在花樣上創新,而不會在老花樣上吊胃口了。