「貪心」做題記錄
-
P2672 [NOIP2015 普及組] 推銷員
由於不會走多餘的路,所以行走產生的疲勞值只和最遠的被推銷的住戶有關。設 \(f_X(i)\) 表示總共選 \(X\) 家住戶,且第 \(i\) 戶是最遠的被推銷的住戶的情況下,最大的疲勞值。顯然可以貪心地在前 \(i-1\) 戶中選擇 \(X-1\) 戶疲勞值最大的住戶,所以 \(f_X(i) = 2S_i + A_i + 前(i-1)個A_j中最大的(X-1)個A_j的和\)。
可以用小根堆(優先佇列)維護前 \(k\) 大和。由於要列舉 \(X\),總的時間複雜度為 \(O(N^2\log N)\),可以得到 60 分。程式碼
下面是本題的正解貪心做法。
考慮貪心地選取前 \(X\) 個疲勞值最大的住戶。但這並不一定是最優的:我們可能選取一個疲勞值更小,但比原來所有住戶都更遠的住戶,從而透過行走距離獲得更大的疲勞值。
具體地,我們要放棄原先的多少個住戶呢?實際上最多放棄 1 個,然後選擇未被選擇的住戶中,\(2S_i+A_i\) 最大的住戶 \(i\)。如果放棄多於 1 個住戶,一定不優於只放棄 1 個。(不知道怎麼表達證明過程)
AC 程式碼
-
[ABC137D] Summer Vacation
如果一個工作在某一天做完之後,直到結束都不能領到工資,那麼還不如不做。所以可以把一個工作可以做的區間看作一段字首。從後往前列舉天數,維護可以做的工作的集合,每次選擇集合中價值最大的工作來做。(不會嚴謹證明,但字首的觀點或許有助於理解。)
提交記錄
-
[AGC004D] Teleporter
洛谷的題解都他媽是什麼東西?怎麼都這麼意識流,還有的是如寫
首先根據題目中圖的性質,可以得出在忽略 \(1\) 出發的邊以後,圖一定是一棵以 \(1\) 為根的內向樹。
證明:刪去 \(1\) 出發的邊顯然不影響其它點到 \(1\) 的可達性,這時邊數 \(=\) 點數 \(-1\)。根據連通性,可以得知圖是樹。再根據除了 \(1\) 之外的點都可達 \(1\),可以得知圖是以 \(1\) 為根的內向樹。
從樹的角度來看,可以得出 \(1\) 必須指向自己。因為在樹上兩點的路徑唯一,而且是內向樹,如果一個點的深度小於 \(K\),並且沒有這個自環,它就無法透過恰好 \(K\) 次傳送到達 \(1\)。
如果一個點的深度大於 \(K\),那麼它顯然無法經過恰好 \(K\) 次傳送到達根節點;否則一定可以,缺少的部分可以透過一直走 \(1\) 的自環來補足。那麼我們把問題轉化成了:給定一棵樹,修改最少的邊,使得所有點的深度不大於 \(K\)。
這個問題還可以進一步轉化:刪除最少的邊,使得剩下的所有弱連通塊(每個弱連通塊都是樹)都滿足以下條件:
- 如果根節點為 \(1\),那麼樹的高度不超過 \(K\)。
- 否則,樹的高度不超過 \(K - 1\)。
採用 dfs ,自下往上解決此問題。如果一棵子樹的高度大於等於 \(K - 1\),並且它的父節點不是 \(1\),那麼需要刪除它連向父節點的邊,否則更新父節點的高度。
function<void(int)> dfs = [&](int u) { h[u] = 0; for(int v: G[u]) { dfs(v); if(u != 1 && h[v] == K - 1) ans++; else h[u] = max(h[u], h[v] + 1); } }; dfs(1);
這樣得出來的圖一定滿足條件,但刪邊數的最少性不會證明。官方題解用的就是這個做法。
完整程式碼