ABC364 DEF 題解

DengStar發表於2024-07-28

ABC364 DEF 題解

D - K-th Nearest

題目連結

賽時想了一個(也許確實是對的)做法,但是程式碼太難寫,一直沒寫出來……看了官方題解才發現正解其實也很簡單……

本題最關鍵的一點是要轉換思路:與其考慮“離某個點第 \(k\) 近的點在哪”,不如考慮“離某個點距離不超過 \(x\) 的點有多少個”。想到這一步轉換,這道題就完成 \(80\%\) 了,因為之後的想法都非常順理成章。

\(f_i(x)\) 表示在 \(A_1, A_2, \cdots, A_n\) 中,離 \(B_i\) 距離不超過 \(x\) 的點的數量。不難看出,只要找到最小的滿足 \(f_i(x) \ge k_i\)\(x\),那麼 \(x\) 就是答案。而 \(f_i(x)\) 顯然是單調遞增的,所以可以二分。

最後,如何計算 \(f_i(x)\) 呢?可以將 \(A_1, A_2, \cdots, A_n\) 從小到大排序,然後我們要做的就是計算落在 \([B_i - x, B_i + x]\) 中的點數。因為 \(A\) 已經有序了,可以簡單地使用 lower_boundupper_bound 來查詢。

AC 記錄

E - Maximum Glutton

題目連結

看到這題,首先想到二維費用揹包:設 \(f(i, x, y)\) 表示在前 \(i\) 道菜餚中,甜度不超過 \(x\),鹹度不超過 \(y\) 的情況下,最多能吃多少道菜餚。需要注意,因為甜度或鹹度超過限度時才會停止,所以在吃掉 \(f(N, X, Y)\) 道菜餚之後,他還可以再吃一道(如果還有菜餚的話),因此答案為 \(\min(N, f(N, X, Y) + 1)\)

但是,二維費用揹包的時間複雜度是 \(O(NXY)\),在本題中無法接受。注意到 \(N\) 很小,這時應該考慮改變狀態設計,並儘可能在下標中用和 \(N\) 有關的量替換掉 \(X\)\(Y\) 有關的量,這樣就可能減小時間複雜度。

重新設計狀態:設 \(f(i, j, k)\) 表示在前 \(i\) 道菜餚中選擇 \(j\) 道吃掉,使得總甜度值為 \(k\) 時,這些菜餚的最小總鹽度值。這樣,狀態的前兩個維度都和 \(N\) 有關,又因為轉移的時間複雜度是 \(O(1)\)(見下),所以總的時間複雜度就降到了 \(O(N^{2}X)\)。想到這樣設計狀態,接下來也就十分簡單了。

答案統計:找到最大的滿足 \(f(N, j, k) \le Y\)\(j\),則答案為 \(\min(N, j+1)\)。(\(0 \le k \le X\)

轉移方程:\(f(i, j, k) = \min(f(i-1, j, k), f(i-1, j-1, k-a_i)+b_i)\),括號中兩項分別考慮選擇和不選擇第 \(i\) 道菜餚。

初始化:\(f(i, j, k) = \begin{cases} 0, j=k=0 \\ +\infty, \text{Otherwise} \end{cases}\)

AC 記錄

最佳化:由於 \(f(i,*,*)\) 只和 \(f(i-1,*,*)\) 有關,所以可以把第一維滾掉,空間複雜度降到 \(O(NX)\)。注意迴圈要改成倒序。

AC 記錄

F - Range Connect MST

題目連結

看到要求最小生成樹,首先想到 Kruskal。先回憶一下 Kruskal 的流程:

  1. 將所有邊按邊權從小到大排序。
  2. 依次遍歷所有邊。對於每條邊,如果它連線的兩點不在同一連通塊中,則把這條邊加入 MST。

對於本題來說,難點在於第二步:邊數最多可以達到 \(NQ\),不可能真的遍歷每條邊。然而,我們真的必須遍歷每條邊嗎?

首先,編號大於 \(N\) 的節點是不重要的,下面我們只關心編號小於 \(N\) 的節點。注意到題目中加邊的形式:每次加邊都會將 \([L_i, R_i]\) 中的點練成一個連通塊。也就是說,如果任意的兩點 \(A\)\(B\) 聯通,那麼所有滿足 \(A \le x \le B\) 的點 \(x\) 都和 \(A\)\(B\) 在同一連通塊中。

以樣例 1 為例,用不同顏色代表不同連通塊:

初始時,所有節點互不連通。

\[{\Huge{\color{red}\Box}{\color{green}{\Box}}{\color{blue}\Box}{\color{brown}{\Box}}} \]

第一次操作,把 \(1\)\(2\) 號節點連通:

\[{\Huge[{\color{green}{\Box\Box}}]{\color{blue}\Box}{\color{brown}{\Box}}} \]

第二次操作,把 \(1\)\(2\)\(3\) 號節點所在的連通塊連通:

\[{\Huge[{\color{blue}{\Box\Box\Box}}]{\color{brown}{\Box}}} \]

第三次操作,把 \(2\)\(3\)\(4\) 號節點所在的連通塊連通:

\[{\Huge{\color{brown}\Box}[{\color{brown}\Box\Box\Box}]} \]

於是可以想到,把邊權排序後,對於每次加邊操作,從 \(L_i\) 開始,不斷跳到當前點所在連通塊最右邊的(即編號最大)節點(不妨稱這個節點的編號為 \(x\)),然後在 \(x\)\(x+1\) 之間連邊,直到跳出加邊的區間。每次加邊的時候,統計一下答案即可。(可以根據上面的圖示理解。)以及,別忘了每次操作時,我們還要從編號大於 \(N\) 的節點向編號小於 \(N\) 的節點連一條邊(否則不連通),統計答案時記得加上。

連通性顯然可以用並查集維護。為了方便快速找到當前連通塊最右邊的節點,可以直接用最右邊的節點作為這個連通塊的代表元,這需要在 merge 的時候注意一下引數的順序(詳見程式碼)。

參考程式碼

PS:ABC352E 也是一道相似的用 Kruskal 求最小生成樹的題,可以去嘗試一下。

相關文章