← 回首頁

雜項(Nim 賽局 / Meet-in-the-Middle 子集和 / QuickSelect / Mo's 演算法)

這頁像一個裝雜物的工具箱,收了四個彼此不相干的聰明捷徑,共通點都是「不想笨笨地把每種可能全算一遍」。Nim 賽局教你一眼看穿拿石子遊戲誰穩贏;Meet-in-the-Middle把太多的組合拆成兩半、分頭算完再湊起來;QuickSelect不必整排排好就能挑出第 k 名;Mo's 演算法把一大堆查詢重新排順序,省下反覆翻找。下面每個都能一步步播放,看它怎麼偷懶得漂亮。

四個各自獨立、互不隸屬的經典技巧:

1. Nim 賽局(多堆 XOR 必勝策略)

每輪計算目前所有堆的 XOR 和 s:s≠0 為先手必勝局,找出最小索引 i 使 pile_i XOR s < pile_i, 從該堆取走 pile_i-(pile_i XOR s) 顆即可維持必勝;s=0 時無必勝步,決定性慣例改為從最小非空堆取走 1 顆。 重複至所有堆為 0(normal play),取走最後一顆的一方獲勝。

固定示範(同 nim.test.js NIM_PILES):piles=[3,4,5],XOR=3⊕4⊕5=2≠0 → 先手必勝
步數:0
紅色(checking)標示本步被取走的堆;長條高度為該堆目前剩餘數量。

目前 XOR 和

0

勝者

對局中

對局紀錄

(空)

2. Meet-in-the-Middle 子集和(折半列舉)

將陣列拆半,分別枚舉左半、右半所有子集和(各 2^(n/2) 個),把右半排序後, 對每個左半和 ls 二分搜尋 need=target-ls;找到即宣告 found=true,全部掃完仍未找到則 found=false。

固定示範(同 mitm-subset-sum.test.js MITM):arr=[3,34,4,12,5,2],target=9(4+5=9 存在)
枚舉數:0
左欄為 leftSums、右欄為 rightSums(右半終態為排序後);反白(active)標示目前二分搜尋的 leftSum 與對應 need。

搜尋狀態

found

搜尋中

3. QuickSelect(隨機化快速選擇,固定種子示範)

以 Lomuto 分割法選定 pivot,將小於 pivot 的元素換到左側;比較分割後的 storeIndex 與 k-1, 相等即為第 k 小,否則遞迴縮小到左半或右半繼續搜尋。

固定示範(同 quickselect.test.js QSEL):arr=[7,10,4,3,20,15],k=3(排序後 [3,4,7,10,15,20],第 3 小=7)
分割輪數:0
紫色(pivot)為本輪 pivot 索引;藍色(comparing)為目前比較的元素;淡出(out)為已排除的搜尋範圍外索引。 pivot 由固定種子(seed=42)LCG 決定,僅供教學示範可重播;實務隨機化 QuickSelect 應使用真隨機亂數。

k

answer

選擇中

4. Mo's 演算法(離線區間相異數)

依 (L 所在塊, R, 原始索引) 排序查詢,初始化空窗 curL=0、curR=-1,逐查詢移動雙指標 (擴右→縮右→擴左→縮左)增量維護 distinct,窗口就緒時記錄該查詢的答案。

固定示範(同 mos.test.js MOS):arr=[1,2,1,3,2,1,4,3],queries=[[0,4],[2,7],[1,5],[4,7]]
指標移動次數:0
藍色外框(range)標示目前雙指標覆蓋的窗口 [curL,curR](初始為空窗 curR=-1)。 此處查詢排序僅依 (L 所在塊, R, 原始索引),未含奇偶塊優化,實務可再省下指標移動次數。

查詢排序後順序(panel.order)

(空)

各查詢答案(依原始索引,panel.answers)

(空)

目前相異數

0