反悔貪心雜題
筆者水平有限,若有筆誤望指出。
筆者認為,反悔貪心其實質僅僅只是一類貪心策略,所謂反悔,僅為一個思維過程,體現為:拿出一個錯誤的貪心策略(很多時候體現為略去某些限制)並嘗試使用更多的策略去修正它
反悔貪心有一個很常見的應用場景:從 \(i\) 的答案修正到 \(i+1\) 的答案,並且往往這個答案具備某種凸性(可能全域性,可能是僅對 \(\pmod x\) 的點有凸性)
同時如果可以設計dp,這個 dp 過程往往也具備凸性(Slope trick?)
關於Slope Trick,斜率最佳化,反悔貪心,費用流(關於流量的最小費用函式是凸的),閔可夫斯基和, WQS 二分 是互相聯絡的。
反悔貪心可以從模擬費用流的角度解釋:將原題建立費用流模型。
下面的敘述中會偏重“修正”思想。
BZOJ1577
一個貪心策略是能上車就上車,這樣顯然是錯的,所以我們走到一個地方後,需要考慮當車塞滿之後,撤銷哪些牛讓本站牛上車更優秀
可以發現,對於車上的牛而言,保留目的地更近的牛必然更優,所以踢掉原本車上的牛(目的地 \(d_1\))加入新的牛(目的地 \(d_2\)),如果 \(d_2<d_1\) 就是更優秀的,可以用一個優先佇列維護這個返回過程。
資料備份
極為經典
給定 \(n\) 個數,現要求選出 \(k\) 個數字,其不相鄰,且權值和最小。
顯然會存在一個 \(O(n^2)\) 的 dp,這裡略去。
我們考慮當前已經選出了 \(i\) 個數字,現在要選出 \(i+1\) 個數字,應當怎麼決策?
-
選出當前可選的最小數字,並將其兩側標記為不可選
-
撤銷掉某次選擇,並選上其相鄰的兩個元素
如果只選擇其中一個,那麼不撤掉一定更優(對另外的點限制更弱且自身更小)
所以考慮維護這個撤銷步驟,樸素的想法是維護兩個堆,一個已經操作的堆,一個尚未操作的堆,取最優決策出來貪心。
但是這樣是有問題的,因為撤銷狀態內部是有聯絡的,譬如你選了 \(x-1,x+1\),在處理撤銷時兩者同時選上 \(x\) 就是錯誤的,會有一個複雜的連鎖反應。
但是注意到這個連鎖反應是成段的,也就是設我當前選了 \(x,x+2,x+4…x+2m\),但是沒有選 \(x-2,x+2m+2\),那麼我撤銷其中任意一個,都會導致當前的方案變為 \(x-1,x+1,x+3\dots x+2m-1,x+2m+1\)(選的個數恰好多一)
所以不妨將這樣的整段限制縮為一個點,這個點的權值就設為兩個方案之間的差值。
並且幸運的是,這個權值是容易計算的。
詳細的說,最初,我們 \(n\) 個數單獨成段,當我們選擇第 \(i\) 段後,將 \(i-1,i+1\) 兩個段吸納進來(作為決策),同時刪去這兩個段並重新編號。
定義一個段的權值是變換操作後的代價與原本代價差,則 \(w'=w_{pre}+w_{suf}-w\)。
正確性是顯然的,每次我們選擇一個段進行操作,都用了最少的代價,且使得選擇個數恰好增加一。
所以得到了一個可以使用連結串列維護段關係的簡潔做法。
事實上,這樣的反悔貪心,往往在堆中的某個點其已經不代表點了,而是一整段的決策
CF436E
我們仍然考慮從 \(i\) 的答案如何推究到 \(i+1\) 的答案,有如下決策:
- 將一個零星變為一星
- 將一個一星改為二星
- 將一個二星改為一星,同時讓一個零星成為二星
- 將一個一星改為零星,同時讓一個零星成為二星。
改動量顯然不會超過兩個點,因為改動更多點都可以被以上四個決策平替掉。
讓我們看看這需要如何維護:
- 維護零星裡最小的 \(a_i\)
- 維護一星裡最小的 \(b_i-a_i\)
- 稍顯複雜,代價是 \(b_i+(a_j-b_j)\),也即維護零星的最小的 \(b_i\),二星的最小的 \(a_j-b_j\)
- 類上,代價是 \(-a_i+b_j\),維護一星的最小 \(-a_i\),零星的最小的 \(b_j\)
也即我們需要 5 個優先佇列維護決策,取最優的擴充套件即可。
CF280D
這是重要模型,是一個絕妙的想法。
考慮全域性問題,我們每次取出最大子段和,然後將這一段取反(乘上 \(-1\)),接著下一次取的時候,如果包含了被取反的段,那麼就相當於扔掉這一部分(刪掉有交的部分),也就是反悔了。
其實是一個反悔機制,新增一段是什麼樣的:
- 選出一段不交的區間
- 刪去原本至多一個區間的一個子區間,並將分裂的兩個區間選出一個進行擴充。
而在這個操作裡,根據最優性,如果這一次連續刪除了兩個之前選出的區間,那麼上一次選這個必然更優,不可能發生,所以只會刪一個。所以我們這樣做是對的(只會增加最多一段區間)
所以只需要維護最大子段和以及取到最大子段和的區間,支援乘上 \(-1\),實現稍有難度
一個絕妙的實現:我們顯然需要維護 \(lx,rx,mx,s,lrx,rlx,ql,qr\),分別表示以 \(l\) 為起點的最大子段和,其右端點是 \(lrx\),\(rx,rlx\) 同理,\(ql,qr\) 維護取到最大子段和的區間。可以照常更新。
但是我們可以維護兩個這樣的量(使用結構體),提前將乘上 \(-1\) 的部分預計算,那麼每次取反相當於交換這兩個量,可以避免維護最小值這種東西
在本題可以這樣寫,然後暴力執行 \(k\) 次即可透過,記得撤銷。
NOIP模擬賽5.繩網委託
題目沒了,我憑記憶描述一下:
給定 \(01\) 序列,定義一次操作為任選 \([l,r]\) 為將區間 \(l,r\) 翻轉,對於 \(k\in [0,n]\),求出操作 \(k\) 次後的最長不降子序列。
考慮最長不降子序列:
可以賦權,將 \(0,1\) 分別賦值為 \(1,-1\),問題就變成了查最大字首。
這也很經典
考慮這類翻轉操作,翻轉 \(k\) 次後,某個字首和是如何組成的呢?
答案是,原本的某個字首,加上 \(k\) 個原序列的不交子區間。這個可以自己手玩得到。
那麼與上一題的區別是,我們 \(k=0\) 時,取出最大字首,將其取反,接下來和上一題就一樣了。
CF335F
335e已經夠噁心了
題意其實是給定序列 \(a\),然後要求將其分為兩個部分 \(S,T\),使得:
- \(|S|\ge |T|\)
- 將 \(x\in S,y\in T,a_x>a_y\) 連邊,存在完備匹配
- \(\sum_{x\in S} a_x\) 最小
假定沒有重複元素,可以想到將餅子按照 \(a\) 從大到小進行排序,設 \(f_{i,j}\) 表示前 \(i\) 個餅子中,還有 \(j\) 個餅子沒有附贈品的最小代價
\(f_{i,j}=\min(f_{i-1,j-1}+a_i,f_{i-1,j+1})\)
可以 \(O(n^2)\) 而且感覺這東西有凸性。
如果有重複元素怎麼辦呢?
那就不妨設 \(f_{i,j}\) 為前 \(i\) 類 餅子,還有 \(j\) 個餅子沒有附贈品的最小代價
那麼有:
仍然可以 \(O(n^2)\) 做。
好了現在我們給出了一個 \(O(n^2)\) 的方法,最佳化路徑只有兩條:
- Slope Trick
- 貪心
好像有凸性,只看 \(j\equiv n\pmod 2\),但是斜率變化量過大,難以使用 Slope Trick。
回到最開始的條件,我們假設不存在完備匹配,應當可以調整
例如如果有 \(x\in S,y\in T,a_x>a_y\) 那麼交換 \(x,y\) 之後如果存在完備匹配,交換後不會變差,同時若不存在完備匹配,肯定是用 \(T\) 的失配值移動過去,甚至可能考慮再換一個過來
考慮我們將一類餅乾新加入決策集合(權值遞減),決策其實並不多:
- 直接給錢
- 讓還沒指定白嫖物件的來白嫖
- 撤掉之前某個白嫖的,連著白嫖兩個
裡面最優秀的顯然還是能夠白嫖就白嫖。
我們考慮維護一個小根堆,裡面維護 當前所有白嫖決策的花費,那麼當前還能夠白嫖的元素是可以計算的:總個數減去兩倍的白嫖次數。
當考慮新加入 \(c\) 個 \(a\) 時,我們先把可以白嫖的存下來(由於權值嚴格偏序,所以不能加入)
然後考慮剩下的不能夠白嫖的。
設 \(v\) 是堆頂,有如下方案:
-
\(v\le x\)(這是可能發生的,會有複雜的白嫖方案)
將其替換為白嫖 \(x\) 是不劣的,買了它後還能多嫖一個,所以還有新的白嫖方案是捨棄 \(v\),嫖兩個 \(x\)
也就是兩個白嫖方案。
-
\(v>x\)
此時考慮 \(w=2x-v\),這是買 \(v\) 白嫖兩個 \(x\) 的收益,我們當前面臨選擇哪個決策更好,我們可以當作白嫖了物品 \(2x-v\) ,因為它對於後續的影響等價於一個物品(如果 \(w>0\) 的話) 和物品 \(v\) 因為它後續影響是一樣的(你可以看作兩個方案之間轉化的代價)
所以有方案 \(v\) 和 \(2x-v\) 兩個。
我們刪去原本的方案,加入這兩個新的白嫖方案即可。
Raper
這題有難度啊。
\(k\) 的限制其實並不容易處理,但是注意到反悔貪心與WQS二分的聯絡,感性理解下 \(k\) 的答案應當是有凸性的(你顯然需要更多的光碟,那麼代價會漲幅得更高(取出最劣的配對方案扔掉))
那麼考慮 WQS 二分,問題就變成了每次配對有一定代價,求最小代價。
考慮當前的光碟可以分為幾類:
- 沒有操作的
- 半成品
- 成品
而我們顯然能夠變成半成品都變,這不會劣,那麼每一時刻只會多一個初始權值 \(A_i\) 的半成品,和一次變成成品的機會 \(B_i\)
有如下決策:
- 將半成品變成成品:\(A_j+B_i-k\)
- 將一個成品的後半工序變成 \(B_i\):\(B_i-B_j\)
- 啥也不幹
用兩個堆,第一個堆維護成品的 \(A\),第二個堆維護成品的 \(B\)。
每次從三類決策裡找到一個更新即可。
校內模擬賽題:樓房搭建
給定序列 \(h\),你最初有一個全零的序列 \(a\),每次可以進行兩次操作之一:
- 選擇 \(i\),\(a_i\leftarrow a_i+1,a_{i+1}\leftarrow a_{i+1}+2\)
- 選擇 \(i\),\(a_i\leftarrow a_i+2,a_{i+1}\leftarrow a_{i+1}+1\)
求使得 \(\forall i,a_i\ge h_i\) 的最小操作次數。
一個很蠢的想法是每次不足就放 \((2,1)\)個,答案顯然是 \(\frac{\sum a}{3}\)。
但可能會在後面造成極大的浪費(中間極高,兩側較小),那麼在這個較高的位置時,我們將之前的 \((i-1,2,1)\) 替換為兩個 \((i-1,1,2)\) 就好了,這使得二樓升高 \(3\)。
但是這樣又會導致給到第三樓 \(i+1\) 的操作空間可能很小(第三樓也很高的話)。
發現第三樓可以在不改變第二樓高度的前提下自己拔高 \(3/6\),是三的倍數,那對答案就不會有影響。
\((i-1,2,1)\) 可以替換為兩個 \((i-1,1,2)\),兩個 \((i-1,1,2)\) 撤掉換成 \((i-1,2,1)\) 後二樓降低 \(3\),一樓不變,可以使用 \(3(i,1,2)\) 讓二樓調整回來,三樓增高 \(6\),也可以被替換為 \((i,1,2)+(i,2,1)\),三樓增高 \(3\)。
也就是說,在當前樓儘量貪,而如果在後面發現不合適(矮了),也可以在不改變上一層和上上一層的條件下只改動這一層(按 \(3\) 的倍數),這就足夠了。
可以存下當前可以執行的 \(+3\) 操作總量,以及當前操作下後一棟樓的基礎高度,就可以計算出還需要多少次 \((2,1)\),以及可能的 \((1,2)\) 進行操作。
如果當前操作下後一棟樓的基礎高度就已經滿足要求了,就可以開始新的一段了(\(+3\) 操作和基礎高度全部清空)
\(Conclusion\)
筆者粗淺的認為:
- 反悔貪心本質其實是在普通貪心的設計上多考慮一個名為撤銷的設計步驟,曰之反悔。
- 其往往答案具備凸性,可以考慮其他操作:WQS等
- 暴力啟發思路
- 考慮局面的變化情況