#41228: c


xsw20080329@gmail.com (敢不敢讓我過)

學校 : 不指定學校
編號 : 242754
來源 : [114.40.63.175]
最後登入時間 :
2024-08-30 19:45:17
o079. 4. 最佳選擇 -- 2024年6月APCS | From: [114.40.32.205] | 發表日期 : 2024-07-12 20:20

演算法構思與程式流程
Comment
有幾個條件限制,讓題目看起來比較混亂。其實找頭尾個一段與找中間一段是一樣的,因為兩者是互補的,找頭尾一段總和不超過lim就等價於找中間一段不小於totalsum-lim。這裡我們可以假設lim≤totalsum,否則將lim改成totalSum就好。講解本題的做法前,先看一下基本型的做法。

基本sliding window
這類型題目的基本型是找出中間一段總和不超過lim的最大總和。

由於是正整數,單一個區間往左右延伸時,其總和必然是單調遞增的。所以很自然地可以用sliding window (或稱雙指針)的方法來做。基本觀念很簡單,我們始終關注一個區間(window) [le,ri],將區間的右端逐步往右移,每次一格,並且調整左端來維護以下的迴圈不變性:
「此區間是右端=ri的條件下,總和不超過lim的最大區間。」

由於前面說到的單調性,假設前面一回合的迴圈不變性已經滿足,當右端往右移動一格時,區間的總和只會變大,它所對應到的最佳左端一定是大於等於之前的左端。我們只需要將左端往右移動到第一個讓總和不超過lim的位置即可。
演算法如下:

best = isum = 0
le = 1
for ri = 1 to n do
    isum += a[ri]
    while isum > lim do // move le 
        isum -= a[le]
        le += 1
    end while
    best = max(best,isum)
end for
由於le與ri都只有往右移動,所以時間複雜度是線性的。

本題的思考方式
與基本型相比,本題有兩個變化:

要找的prefix與suffix,而非中間一段。
所選取的兩段中奇數與偶數個數必須相等。
其實原理還是一樣,為了滿足奇偶個數的條件,我們對每一個prefix與suffix以它們的奇數個數減去偶數個數(以下稱為dif值)次予以分類擺放,相同dif值的前綴和放在同一個串列。對於每一個後綴,它的dif值如果是d,那麼我們只能在dif值為−d的前綴中尋找與他的搭配,因為這樣奇數與偶數個數才會相等。

如何在對應的串列中找到最佳搭配的前綴呢?我們的目標是前綴與後綴的和不超過某個lim且越大越好,所以後綴和如果是s,我們要找的前綴就是其和 ≤lim−s
的最大值。
當然不能用線性搜尋或枚舉比對,因為時間太慢。記得我們的前綴放入串列中都是單調遞增的,所以可以用二分搜來找。
事實上不需要,俗話說:「連續二分搜不如一路爬過去」,我們由小到大產生所有的後綴和,所以要找的前綴和在每個串列中是單調下降的。每次我們只要在對應的串列中比對最後一個前綴和,如果這個前綴和太大(>lim−s),那麼它對之後的後綴也太大,我們可以直接將其刪除。

最後一個問題是如何儲存這些前綴和的串列呢?同一個串列的dif值是相同的,本題保證所有prefix的dif值的絕對值不超過M=2000,所以前綴的dif值的範圍是−M~M,我們可以用2M+1個串列來儲存,dif值為i的放在第i+M個串列。


整理一下,算法流程如下:由小到大,對於每一個prefix,計算其總和以及奇數個數減去偶數個數dif值),依照dif值放入對應的串列;由短到長掃描所有suffix,計算其總和s與dif值d。

在prefix dif值為−d的串列(就是第M−d的串列)中,由後往前刪除總和過大(>lim−s)的prefix sum。如果有滿足要求的答案(串列非空),就嘗試更新最佳解。
注意到,每一個串列我們只在其後增加元素或刪除最後一個元素,所以其實是當做stack在使用。

 
ZeroJudge Forum