contents
Problem
背景
動畫 遊戲人生《No Game No Life》中,史蒂芙 (Stephanie Dola) 常常被欺負,儘管她以學院第一畢業,對於遊戲一竅不通的她在這個世界常常被欺負。現在就交給你來幫幫她。
問題描述
兩個人輪流在一個大棋盤上下棋,每一步棋的得分根據這一步棋與最鄰近的敵方棋子的曼哈頓距離。
對於兩個點 $p, q$ 座標 $(p_x, p_y), (q_x, q_y)$,曼哈頓距離 (Manhattan distance) 為 $|p_x - q_x| + |p_y - q_y|$。
Sample Input
|
|
Sample Output
|
|
Solution
把鄰近搜索問題做個總結,普遍處理的是靜態資料跟單一詢問,而在最近餐館那一題已經用 KD-tree 處理過 KNN 問題。這一題是採用動態插入和詢問以及數學性質較強的曼哈頓距離,離線處理也是個選擇。
此問題限制在 $n = 50000$ 的情況下,進行測試討論,除了分桶、方格法外,探討三種思路:
Dynamic KD-tree 利用替罪羊樹的概念完成,看著卦長的代碼以及卡車口述概要,終於敲敲打打拼湊起來,掛上啟發式的搭配具有不錯的成效。空間複雜度 $O(n)$,插入複雜度 $O(\log^2 n)$,查詢 $O(\log n)$ (據說是在曼哈頓距離下的緣故),速度是暴力法 $O(n^2)$ 二十倍左右。
Segment tree + 平衡樹,空間複雜度 $O(n \log n)$,時間複雜度 $O(\log^3 n)$,使用座標轉換將菱形轉換成正方形,套上二分邊長去查找區域內部是否有點。由於 $n$ 的緣故,速度比 Dynamic KD-tree 慢上許多,若用暴力法 $O(n^2)$ 只快兩倍之多。實作測試提供者 liouzhou_101。
離線處理 CDQ 分治,空間複雜度 $O(n)$,總時間複雜度 $O(n \log^2 n)$,採用思路為曼哈頓距離切割成四個象限進行極值查找。比暴力法快十倍所右。
前兩個作法比較裸,在此特別補充 CDQ 分治,曼哈頓距離可以考慮成四個象限,詢問 $(x, y)$ 的最鄰近點,首先考慮左下角 $(x', y')$,亦即 $x' \le x, \; y' \le y$,則曼哈頓距離 $dist = (x - x') + (y - y') = (x + y) - (x' + y')$,明顯地求最近距離要讓 $x' + y'$ 最大化。同理其他象限。
為了解決這詢問,套用 CDQ 分治,按照 $x$ 座標排序,接著二分操作順序,切割操作 $[l, mid], [mid+1, r]$,在左右兩塊仍然按照 $x$ 排序。單獨看 $[mid+1, r]$ 的操作會受 $[l, mid]$ 和自己本身影響,對於前者而言,採用歸併排序那樣,按照 $x$ 座標慢慢合併 (概念上),合併過程套用 Binary indexed tree 進行極值查找。對於後者,就進行遞迴求解,明顯地 $[l, mid]$ 只會受 $[l, mid]$ 影響。
CDQ 分治的概要,按照其中一個關鍵排序,接著二分操作順序進行分置處理。國外是有論文在描述這個 Online to Offline 的算法,CDQ 命名就是人名,會給國外看笑話吧。
|
|
備註「欸欸,加上悔棋的話,是不是持久化 kd-tree」
實作探討
關於 kd-tree 實作細節探討,與通常會犯的錯誤,關係到速度有常數差異。
在 closest()
中,常犯的錯誤是 探索順序 ,盡可能先靠近,啟發式才能更加快速,別像我打出錯誤的搜索順序如下:
|
|
實作的順序應該如下,別總是先探訪左子樹、在去探訪右子樹,kd-tree 必須注意順序。
|
|
假設資料大小 sizeof(dim_element)
很大,通常會利用指針陣列來進行排序,這樣可以降低搬運大型資料複製時間,但奇怪的是由於指針陣列佔有一定空間,索引資料時又會佔據一段空間,估計是快取方面出了點問題 (或者是我寫不好),導致直接搬運資料是來得比較快速,這個修改在 b348. 最近餐館 也有進行測試,速度有提升。
接著可以藉由函數參數少量,來拉快程式在堆疊參數所需要的時間,在節點內部宣告採用維度 d
|
|
這個修改造成詢問時,不僅僅在走訪傳遞參數少了一個,還少 k+1
的計算。測試結果中,光靠這一點速度沒有明顯提升。kd tree 還有一個靠臉吃飯的邊界分割,要是相同時分左分右,這一點是最痛苦的,在此就不去討論,當然可以利用隨機擾動來解決這問題。
至於要使用 sort()
進行 $O(n \log n)$、還是使用 nth_element()
的 $O(n)$ 找到中位數,根據兩題的測試,由於 $n$ 都不大,照理來講 nth_element()
快於 sort()
,但根據實際測試於 liouzhou_101 的代碼,sort()
的速度會比較快,其一是運氣、其二是未知情況。就從以下代碼中,差異並不明顯。
Dynamic Kd tree
沒有提供垃圾回收,靠內存持運作,要是 RE 就放大一點。
|
|
CDQ 分治
|
|