根號算法 I
Sqrt Decomposition & Techniques I
Gino
\[ \sqrt{ }\]
內容大綱
★☆☆☆☆ 折半枚舉
★★☆☆☆ 序列分塊
★★☆☆☆ 塊狀鍊表
★★★☆☆ 值域分塊
★★★☆☆ 數論分塊
★★★★★ 根號分 case
★★★★★ 操作分塊
★★★★☆ 樹分塊
★★☆☆☆ 基礎莫隊算法
★★★★☆ 樹上莫隊
★★★★★ 回滾莫隊
★★★★★ 帶修改莫隊
根號算法 I
根號算法 II
折半枚舉
Meet In The Middle
例題
有 \(N\) 個正整數 \( a_1, a_2, ..., a_N\)
給定 \( x \),請問有多少子集
滿足子集和等於 \(x\)?
- 子題一:\(N \leq 20 \)
- 子題二:\(N \leq 40 \)
子題一很簡單
\(O(2^N)\) 暴力枚舉 + \(O(N)\) 檢查
複雜度 \(O(N \cdot 2^N) \)
子題一很簡單
\(O(2^N)\) 暴力枚舉 + \(O(N)\) 檢查
複雜度 \(O(N \cdot 2^N) \)
子題二?
把 \(N\) 個數字對半拆成兩組:A 組跟 B 組
A
B
\(a_1 \sim a_{\frac{N}{2}}\)
\(a_{\frac{N}{2}+1} \sim a_{N}\)
1. 暴力枚舉 A 的所有子集
對每個子集算出他的和
把這些和儲存在一個 set 裡面
A
B
\(a_1 \sim a_{\frac{N}{2}}\)
\(a_{\frac{N}{2}+1} \sim a_{N}\)
2. 暴力枚舉 B 的所有子集
對每個子集算出他的和
接著搜尋剛剛的 set 有多少數字跟它互補(相加為 \(x\))
A
B
\(a_1 \sim a_{\frac{N}{2}}\)
\(a_{\frac{N}{2}+1} \sim a_{N}\)
1. 暴力枚舉 A 的所有子集
對每個子集算出他的和
把這些和儲存在一個 set 裡面
A
B
\(a_1 \sim a_{\frac{N}{2}}\)
\(a_{\frac{N}{2}+1} \sim a_{N}\)
時間複雜度
\(O(2^{N/2} (N + \text{set 操作複雜度}))\)
2. 暴力枚舉 B 的所有子集
對每個子集算出他的和
接著搜尋剛剛的 set 有多少數字跟它互補(相加為 \(x\))
A
B
\(a_1 \sim a_{\frac{N}{2}}\)
\(a_{\frac{N}{2}+1} \sim a_{N}\)
時間複雜度
\(O(2^{N/2} (N + \text{set 操作複雜度}))\)
總時間複雜度
\(O(2^{N/2} (N + \text{set 操作複雜度}))\)
會把折半枚舉放在根號是因為 \(2^{N/2}\) 剛好是 \(2^N\) 開根號
因為可能有多個子集和是重複的
所以可以用 map<int, int> 紀錄和還有對應的出現次數
時間複雜度:\(O(2^{N/2} \cdot (N + \log 2^{N/2}) \in O(N \cdot 2^N)\)
實作細節
\(\Uparrow\) 用對數律把\(N/2\) 拿下來
算每個子集的和可以用 DP 的方式
\(sum[mask] = sum[mask \oplus anybit] + a[anybit]\)
\(anybit =\) 任意取一個值為 true 的位元
常數優化 I
位元的可以取最高的位元
可以用 __lg(mask) 得到每個 mask 的最高位元 index
所以可以這樣寫:
常數優化 I
如果用 __lg(mask) 被卡常的話
可以開大小 \(2^{N/2}\) 的陣列預處理每個 mask 的最高位元
位元也可以取最低的位元
有寫過 BIT 的話就會知道 \(x\) 的 lowbit 是 \(x \ \& \ (-x)\)
常數優化 I
map 改用 vector
枚舉完其中一邊的子集後對 vector sort
接下來要查詢數量可以用 upper_bound 跟 lower_bound
常數優化 II
枚舉 A 跟 B 的子集,把這些和存成兩個 vector
把這兩個 vector 都 sort
接著用雙指針算答案
常數優化 III
要算出所有子集和可以不要用 0/1 枚舉
改用倍增的方式
接著搭配優化 III
想了解的話可以參考 code
常數優化 IV
根號算法 I
By Gino
根號算法 I
附中 x 延平競程讀書會簡報
- 88