給出乙個長度為 \(n\) 的序列 \(h\),請將 \(h\) 分成若干段,滿足每段數字之和都不超過 \(m\),最小化每段的最大值之和。眾所周知,不會做的題目就 \(dp\)。其實 \(dp\) 方程挺好想的,設 \(f_i\) 為到第 \(i\) 個數,分為若干段所需要的最小代價;設 \(sum_\) 為\(h\) 的字首和。根據題意可以得到轉移:
\[f_i=\min\limits_\left(f_j+\max\limits_^h_k\right)
\]這個式子有很多種處理方法,最簡單的直接 \(cdq\) 就可以了,線段樹題解區的大佬也講過了,我翻提交記錄好像看見了有用堆寫的。如果用堆的話,是沒有考慮好本題的單調性。
本文要提的是題解區的那個 \(o(n)\) 做法。由於這位大佬的題解有一點久遠,意思模糊不清,然後還有證明沒給出,因此本文重提。
這種做法大概可以理解為用單調性對堆優化吧……
引理一:\(\max\limits_^h_k\) 中,隨著 \(k\) 的增大,其值單調不增。
引理二:\(f\) 單調不降。參考 csp-s 2020 t4,我們可以把轉移分成兩段。
1、對於乙個合法的 \(j\),\(h_\leq \left(\max\limits_^h_k\right)\),則 \(\left(\max\limits_^h_k\right)=\left(\max\limits_^h_k\right)\);又因為 \(f\) 單調不降,\(f_j\leq f_\)即對於轉移 \(f_j+\left(\max\limits_^h_k\right)\leq f_+\left(\max\limits_^h_k\right)\) 。所以此時從 \(j\) 轉移(\(j+1\) 到 \(i\) 為一段)比從 \(j+1\) 轉移更優。
那麼此時對於 \(h\) 的乙個極小值就可能成為轉移的最優解。又由於 \(h\) 單調不增,所以可以對 \(h\) 維護乙個單調遞減的佇列,隊尾即轉移的可能最優點。
2、對於乙個合法的 \(j\),\(h_>\left(\max\limits_^h_k\right)\),可以包含在第一種情況,即我們假設單調佇列 \(q\),設左右端點 \(l,r\),則最優的轉移會在 \(q_\) 之中;也即 \(h_>h_}>\dots>h_\) 當中轉移 \(f_i\)(不取等是因為取等的情況一定靠前的更優)。
具體的證明大概是這樣的:第一種情況維護的是函式的極小值,其中突出的部分會被包含在兩個相鄰的極小值當中,但這種轉移對佇列中的 \(q_l\) 不適用!
因為這種情況即從 \(q_l\) 轉移到 \(i\),那麼 \(q_l\) 之前的位置應當為第乙個位置 \(st\) 使得 \(h_i-h_>m\),\(f_i\) 就更新為 \(f_+h_\),否則就越界了。
但維護單調佇列的時候就可能產生一些意外使得一些值沒被更新到。所以可以維護單調棧防止漏掉情況。具體是這樣的,對於第二種情況,有可能直接 \(h_\) 自己作為一段新的段,幷包含之後的數,也就會存在一段連續相等的 \(f\);或者把之前並作一段,這並不會影響轉移。
我們發現,當佇列元素越多,重構單調棧的次數也就越多,但總的重構次數也越少(僅在對佇列彈出過期元素可能重構)。可以考慮把隨機資料拆成幾段單調下降的序列,最長的最多是最長下降子串行,假設長度為 \(p\)。則最多重構 \(n/p\) 次,每次最多重構 \(p\) 個數,則複雜度最多 \(o(n/p·p)=o(n)\)。取到最大時,當最長子序列盡量長,也即 \(h\)嚴格單調下降。當然並不是嚴格單調下降就可以取到最大值,還要考慮 \(h\) 和 \(m\) 之間的關係。(純屬口胡)
那麼考慮 \(h_i\),可以 \(st+1\sim i\) 並成一段,\(f_i=f_+h_\);若單調棧中有值,則可以在其他的 \(j>st\) 並成一段 \(h\sim i\),取最小即可。
可能有人有疑問這裡的第一種情況去哪了?其實在一開始維護第一種情況就直接扔到單調棧裡就可以了,反正最後是維護最小的。(根據上文提到的單調性,顯然是正確的)
(如果後面想到更好的對單調棧的解釋我會回來補充的,不過 \(noip\) 之後可能就 \(afo\) 了)
綜上我們得到了乙個 \(o(n)\) 的演算法。
我知道你們只看這個
/*
by xiejinhao
2020-11-20 9:19 from xwsf
*/#include using namespace std;
const int n = 1e5 + 10;
int h[n], q[n], stk[2][n];
int top[2], l = 1, mid, r;
long long f[n], tmpf[n];
// stk[0/1] 左/右 棧,指標對應 top[0/1]
// q 佇列 h 原陣列 tmpf 對應佇列中的 f 值
void push(int x, int i)
void rebuild()
int main() // 維護單調佇列
if(l > r) tmpf[r + 1] = f[st - 1] + h[i];
else tmpf[r + 1] = f[q[r]] + h[i];
q[++r] = i, push(r, 1);
// 隊頭的情況要特判
if(stk[0][top[0]] == l) --top[0];
if(stk[1][top[1]] == l) --top[1];
while(l <= r and q[l] < st) // 彈出過期元素
f[i] = f[st - 1] + h[q[l]]; // 和開頭並為一段
// --- 與開頭之後的某個數開始並為一段 ---
if(top[0]) f[i] = min(f[i], tmpf[stk[0][top[0]]]);
if(top[1]) f[i] = min(f[i], tmpf[stk[1][top[1]]]);
} printf("%lld\n", f[n]);
return 0;
}
1、本文同步發布在我的:點我
2、翻了下提交記錄,各位以後抄題解稍微改一下行嘛
3、能點個贊嗎(光速逃
TJOI2011 樹的序(貪心,笛卡爾樹)
眾所周知,二叉查詢樹的形態和鍵值的插入順序密切相關。準確的講 1 空樹中加入乙個鍵值k,則變為只有乙個結點的二叉查詢樹,此結點的鍵值即為k 2 在非空樹中插入乙個鍵值k,若k小於其根的鍵值,則在其左子樹中插入k,否則在其右子樹中插入k。我們將一棵二叉查詢樹的鍵值插入序列稱為樹的生成序列,現給出乙個生...
1228 書架 題解
題目描述 輸入輸出 樣例輸入 樣例輸出 想法程式注意 題目描述 john最近買了乙個書架用來存放奶牛養殖書籍,但書架很快被存滿了,只剩最頂層有空餘。john共有n頭奶牛 1 n 20,000 每頭奶牛有自己的高度hi 1 hi 10,000 n頭奶牛的總高度為s。書架高度為b 1 b s 2,000...
題解 ZJOI2006 書架
link 小 t 有乙個很大的書櫃。這個書櫃的構造有些獨特,即書櫃裡的書是從上至下堆放成一列。她用 1 到 n 的正整數給每本書都編了號。小 t 在看書的時候,每次取出一本書,看完後放回書櫃然後再拿下一本。由於這些書太有吸引力了,所以她看完後常常會忘記原來是放在書櫃的什麼位置。不過小 t 的記憶力是...