題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。
Problem Statement
題目簡述
有 N N N 個麻糬,大小已依非遞減順序排列。若一個麻糬的大小至多是另一個麻糬的一半,就能把較小者放在較大者上做成一組鏡餅。
每個麻糬最多只能使用一次,請求出最多可以同時做出幾組鏡餅。
Constraints
約束條件
2 ≤ N ≤ 5 × 1 0 5 2 \leq N \leq 5 \times 10^5 2 ≤ N ≤ 5 × 1 0 5
1 ≤ A i ≤ 1 0 9 1 \leq A_i \leq 10^9 1 ≤ A i ≤ 1 0 9
A i ≤ A i + 1 A_i \leq A_{i+1} A i ≤ A i + 1
所有輸入皆為整數
思路:貪心配對與可行性檢查
題目的核心是:從已排序的序列中選出若干小麻糬作為上層、同樣數量的大麻糬作為下層,並讓每一組都滿足「上層大小的兩倍不超過下層大小」。
基於貪心策略,如果要做出 K K K 組鏡餅,最不浪費的選法是讓最小的 K K K 個麻糬 去當上層、最大的 K K K 個麻糬 去當下層。如果這種極端的分配方式都無法滿足條件,換成更大的上層或更小的下層必然也會失敗。
方法一:二分答案
可行性具有單調性:若能做出 K K K 組鏡餅,則必然也能做出 K − 1 K-1 K − 1 組。因此我們可以用二分搜尋來尋找最大的可行組數 K K K 。
在驗證候選數量 K K K 是否可行時,我們將序列中最前方的 K K K 個麻糬(作為上層)與最後方的 K K K 個麻糬(作為下層)一對一依序檢查。只要每一對都滿足「上層的兩倍不大於下層」,就代表這個 K K K 是可行的。
候選數量可以是 0 0 0 ,此時檢查應自然視為可行;二分範圍的右界最多是 ⌊ N / 2 ⌋ \lfloor N / 2 \rfloor ⌊ N / 2 ⌋ ,因為每組鏡餅都需要兩個麻糬。
複雜度分析
時間複雜度:O ( N log N ) \mathcal{O}(N \log N) O ( N log N ) ,二分搜尋 O ( log N ) \mathcal{O}(\log N) O ( log N ) 次,每次檢查 O ( K ) ≤ O ( N ) \mathcal{O}(K) \le \mathcal{O}(N) O ( K ) ≤ O ( N ) 。
空間複雜度:O ( 1 ) \mathcal{O}(1) O ( 1 ) 。
方法二:二分答案 + 雙指標與前綴最大值預處理
第二種方法仍然基於二分答案,差別在於我們先預處理出每個潛在上層的配對需求。這樣一來,每次驗證 K K K 時就不必重新掃描 K K K 對麻糬,能直接以 O ( 1 ) \mathcal{O}(1) O ( 1 ) 時間得出結論。
我們可以進一步優化驗證過程。針對每個可能作為上層的麻糬,可以利用雙指標找出它在序列中「最早能合法配對的下層位置」。
狀態定義:
為了方便對照實作與理解,我們依序定義以下狀態:
最早合法位置 (n x t [ i ] nxt[i] n x t [ i ] ) :對於第 i i i 個麻糬,找到滿足大小條件的最小下層索引 j j j 。若不存在則為 N N N 。
跨越距離需求 (g a p [ i ] gap[i] g a p [ i ] ) :該麻糬要配對到合法下層,至少需要在陣列中往右跨越的距離,即 g a p [ i ] = n x t [ i ] − i gap[i] = nxt[i] - i g a p [ i ] = n x t [ i ] − i 。
前綴最大需求 (p r e M a x [ i ] preMax[i] p r e M a x [ i ] ) :前 i i i 個麻糬中,最大的跨越距離需求。
檢查條件推導:
若我們打算用最前方的 K K K 個麻糬(索引 0 … K − 1 0 \dots K-1 0 … K − 1 )來當上層,這群上層當中最嚴苛(需要跨越最遠)的距離需求,就是該截斷區間的「前綴最大需求」p r e M a x [ K − 1 ] preMax[K-1] p r e M a x [ K − 1 ] 。
為了確保這 K K K 個上層都能在剩餘的序列中找到對應下層,且不會超出陣列總長度,這 K K K 個上層的最右側索引 ( K − 1 ) (K-1) ( K − 1 ) 加上這段區間的最大跨越距離 p r e M a x [ K − 1 ] preMax[K-1] p r e M a x [ K − 1 ] ,不能超過陣列的最大索引 ( N − 1 ) (N-1) ( N − 1 ) 。
也就是說,能組成 K K K 組鏡餅的條件為:
( K − 1 ) + p r e M a x [ K − 1 ] ≤ N − 1 (K - 1) + preMax[K - 1] \le N - 1
( K − 1 ) + p r e M a x [ K − 1 ] ≤ N − 1
化簡後為:
p r e M a x [ K − 1 ] ≤ N − K preMax[K - 1] \le N - K
p r e M a x [ K − 1 ] ≤ N − K
若條件成立,則這 K K K 組鏡餅必定能順利配對。透過 O ( N ) \mathcal{O}(N) O ( N ) 事先算好每個前綴區間的最大需求後,後續每次檢查 K K K 的合法性只需 O ( 1 ) \mathcal{O}(1) O ( 1 ) 時間。
複雜度分析
時間複雜度:O ( N ) \mathcal{O}(N) O ( N ) ,其中雙指標預處理和前綴最大值計算各 O ( N ) \mathcal{O}(N) O ( N ) ,二分搜尋 O ( log N ) \mathcal{O}(\log N) O ( log N ) 次,每次檢查 O ( 1 ) \mathcal{O}(1) O ( 1 ) 。
空間複雜度:O ( N ) \mathcal{O}(N) O ( N ) ,用於儲存 n x t nxt n x t 與 p r e M a x preMax p r e M a x 陣列。
Code
方法一:二分答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 N = int (input ()) A = list (map (int , input ().split())) def check (k ): for i in range (k): if A[i] * 2 > A[N - k + i]: return False return True left, right = 0 , N // 2 while left <= right: mid = (left + right) // 2 if check(mid): left = mid + 1 else : right = mid - 1 print (right)
方法二:預處理最早可配位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 N = int (input ()) A = list (map (int , input ().split())) nxt = [0 ] * N right = 0 for i in range (N): while right < N and 2 * A[i] > A[right]: right += 1 nxt[i] = right gaps = [nxt[i] - i for i in range (N)] pre = [0 ] * N pre[0 ] = gaps[0 ] for i in range (1 , N): pre[i] = max (pre[i - 1 ], gaps[i]) def check (k ): return k - 1 + pre[k - 1 ] <= N - 1 left, right = 0 , N // 2 while left <= right: mid = (left + right) // 2 if check(mid): left = mid + 1 else : right = mid - 1 print (right)