題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。
Problem Statement
題目簡述
有 N 隻排成一排的奶牛,每隻奶牛有其對應的效率值 Ei。我們需要從中挑選若干隻奶牛,使得任意連續被選中的奶牛數量不超過 K 隻。求能獲得的最大效率總和。
Constraints
約束條件
- 1≤N≤105
- 1≤K≤N
- 0≤Ei≤109
思路:單調佇列優化 DP
1. 狀態設計:枚舉上一頭休息的奶牛
這題的限制是:不能選出超過 K 隻連續的奶牛。換句話說,如果目前考慮到第 i 隻奶牛,最後一段被選中的奶牛長度不能超過 K。
設 f[i] 表示前 i 隻奶牛最多能獲得的總效率。為了描述最後一段連續被選中的奶牛,我們枚舉「上一頭休息的奶牛」的位置 j。
若第 j 隻奶牛休息,則第 j+1 隻到第 i 隻奶牛都可以被選中。為了不違反連續數量限制,位置 j 必須滿足:
- i−K≤j≤i,也就是最後連續被選中的奶牛數量不超過 K。
- j 可以等於 i,表示第 i 隻奶牛本身休息,此時最後一段被選中的奶牛數量為 0。
因此,狀態轉移可以寫成:
f[i]=j=i−Kmaxi(f[j−1]+t=j+1∑iEt)
其中,Et 表示第 t 隻奶牛的效率。
任一合法方案在前 i 隻奶牛中,都可以看成「前面一段合法方案」加上「最後一段連續被選中的奶牛」。
只要找到最後一段開始前那頭休息的奶牛,就能唯一描述這次轉移;而 j=i 又涵蓋了「目前這頭不選」的情況。
2. 前綴和化簡轉移式
直接計算區間效率和會讓每次轉移都需要額外枚舉一段區間。為了把區間和變成 O(1) 查詢,定義前綴和:
s[i]=t=1∑iEt
那麼第 j+1 隻到第 i 隻奶牛的效率和就是:
t=j+1∑iEt=s[i]−s[j]
代入原本的狀態轉移:
f[i]=j=i−Kmaxi(f[j−1]+s[i]−s[j])
由於 s[i] 對所有候選的 j 都相同,可以提出到最大值外面:
f[i]=s[i]+j=i−Kmaxi(f[j−1]−s[j])
這一步是整題的關鍵:我們不再需要重新計算每段連續奶牛的效率,只需要在一個會滑動的區間中,找出 f[j−1]−s[j] 的最大值。
3. 單調佇列優化
令輔助狀態值為:
w[j]=f[j−1]−s[j]
則轉移式可以寫成:
f[i]=s[i]+j=i−Kmaxiw[j]
如果 DP 轉移長得像「目前位置的固定項 + 某個滑動窗口內的最大值 / 最小值」,通常就可以考慮用單調佇列優化。
本題中,隨著目前位置 i 往右移動,查詢區間 [i−K,i] 也會同步向右平移,所以正好是滑動窗口最大值模型。
我們維護一個雙端佇列,佇列中保存候選位置,並讓這些位置對應的輔助狀態值保持單調遞減。這樣一來,佇列頭部就永遠是目前窗口內的最大值。
每處理一個新位置時,需要做三件事:
- 先計算目前位置作為「休息位置」時的輔助狀態值,並加入佇列。
- 加入前從佇列尾部移除所有不如目前位置優的候選,維持單調遞減。
- 從佇列頭部移除已經不在 [i−K,i] 範圍內的候選。
完成後,佇列頭部對應的輔助狀態值就是轉移所需的最大值。
按照上述定義,當 j=0 時,需要使用:
f[−1]−s[0]
這在一般陣列下標中會出現越界。為了讓轉移在邊界處也一致,我們額外定義:
w[0]=0
可以理解為:在還沒選任何奶牛之前,前面的最大效率是 0,前綴和也是 0。這樣一來,當前 i 隻奶牛全部都被選中且 i≤K 時,也能透過 j=0 這個候選位置自然轉移得到答案。
佇列一開始必須放入位置 0,否則前 K 隻奶牛全部被選中的情況就無法被正確轉移到。
複雜度分析
- 時間複雜度:O(N)。每隻奶牛的下標最多入隊一次、出隊一次,單調佇列的操作均攤為 O(1),因此總時間複雜度為線性。
- 空間複雜度:O(N)。需要前綴和陣列、DP 狀態陣列、輔助狀態陣列以及單調佇列,空間複雜度為線性。
Code
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 32 33
| from collections import deque
n, k = map(int, input().split()) E = [0] + [int(input()) for _ in range(n)]
s = [0] * (n + 1) for i in range(1, n + 1): s[i] = s[i - 1] + E[i]
f = [0] * (n + 1)
w = [0] * (n + 1)
q = deque([0]) for i in range(1, n + 1): w[i] = f[i - 1] - s[i] while q and w[q[-1]] <= w[i]: q.pop() q.append(i)
while q and q[0] < i - k: q.popleft() f[i] = s[i] + w[q[0]]
print(f[n])
|