1. 程式人生 > 實用技巧 >「學習筆記 - 動態規劃」拆塊最大子段和

「學習筆記 - 動態規劃」拆塊最大子段和

Background

最大子段和是最經典的 dp 問題了,但是最近書蟲發現了最大子段和的另一個拓展演算法 —— 拆塊最大子段和。

拆塊最大子段和可以用兩步,書蟲將其命名為:

  1. 拆開
  2. 組合

接下來我們將用一些例子來講解這個演算法。

Sample 0 P1115 最大子段和

Link

Description

給定一個長度為 \(n\) 的序列 \(a_i\),求出一段使得這一段的和最大。
\(1 \le n \le 2 \times 10^5\)\(|a_i| \le 10^4\)

Solution

最大子段和板子,考慮 dp,定義 \(dp[i]\)\([1,i]\) 之間的最大子段和,那麼對於第 \(i\)

個位置,可以考慮從 \(dp[i-1]\) 後面接 \(a[i]\),也可以選擇單獨開始一個子段,因此狀態轉移方程很容易就得出來了:

\[dp[i]=\max\{dp[i-1],0\}+a[i] \]

注:下文中 \(\max\limits_{i \in [l,r]}[a[i]]\) 代表 \(a[l]\)\(a[r]\) 的最大子段和。

Sample 1 P2545 [AHOI2004]實驗基地

Step 1:Link
Step 2:Link Step 1 的加強版,想投主題庫沒過(

Description

給定一個 \(2 \times n\) 的矩陣,第 \(i\) 行第 \(j\)

列的數為 \(a_{i,j}\)

求一個凹形塊使得凹形塊裡的數字和最大。

凹形塊定義為一個 \(2 \times w_1\) 的矩形,其中 \(3 \le w_1 \le n\),然後在第一行把一塊 \(1 \times w_2\) 的矩形挖掉,其中 \(1 \le w_2 \le n-2\),要保證挖掉之後第一行左右都有殘留的部分。

Step 1:\(n \le 3000\)
Step 2:\(n \le 5 \times 10^6\)

Solution for Step 1

凹形塊就是類似下面這個圖形:

---++---+++-
---++++++++-

我們嘗試 拆開,也就是拆塊最大子段和的第一步:

---++ --- +++-
---++ +++ +++-

如果我們不考慮第二步 組合,那麼可以用一個 \(\mathcal O(n^2)\) 的做法完成。

拆成的三部分中間的部分是列舉的部分,假設他為 \([l,r]\),那麼答案可以由三部分組成:

  1. 左邊的是 \(\max\limits_{i \in [1,l-1]}[a[i][1]+a[i][2]]\)
  2. 中間是 \(\displaystyle \sum\limits_{i=l}^r a[i][2]\)
  3. 右邊的是 \(\max\limits_{i \in [r+1,n]}[a[i][1]+a[i][2]]\)

因此,我們只需要列舉中間的區間 \([l,r]\) 即可,時間複雜度 \(\mathcal O(n^2)\)

這個演算法可以輕鬆通過 Step 1。

Solution for Step 2

\(\mathcal O(n^2)\) 會炸掉,我們需要 \(\mathcal O(n)\)

我們發現上一個 Solution 僅僅是 拆開,沒有 組合

所以我們將這個凹形塊重新拆開,省去左右的空白:

++ --- +++
++ +++ +++

拆開後,我們將其一一組合,發現有三種組合方式:

  1. 左,將其稱為單獨塊,定義 \(dp[i][1]\)\(\max\limits_{k \in [1,i]}[a[k][1]+a[k][2]]\)
  2. 左 + 中,將其稱為 L 形塊,定義 \(dp[i][2]\)\([1,i]\) 中的最大 L 形塊。
  3. 左 + 中 + 右,即為凹形塊,定義 \(dp[i][3]\)\([1,i]\) 中的最大凹形塊。

不難發現,這三塊可以同時計算:

  1. 左的單獨塊就是普通的最大子段和。
  2. 左 + 中的 L 形塊可以是從左的單獨塊接上一個 \(a[i][2]\) 或者左 + 中的 L 形塊接上一個 \(a[i][2]\),即為:

\[dp[i][2]=\max\{dp[i-1][1],dp[i-1][2]\}+a[i][2] \]

  1. 左 + 中 + 右的凹形塊可以是從左 + 中的 L 形塊接上一個 \(a[i][1]+a[i][2]\) 或者左 + 中 + 右的凹形塊接上一個 \(a[i][1]+a[i][2]\),即為:

\[dp[i][3]=\max\{dp[i-1][2],dp[i-1][3]\}+a[i][1]+a[i][2] \]

我們就可以 \(\mathcal O(n)\) 計算了,回顧本題,我們將其 拆開 為三塊,然後 組合 計算,很容易就完成了拆塊最大子段和。

是不是還挺簡單的?

Practice 1 P7160 「dWoi R1」Sixth Monokuma's Son

Link

這題將不會詳細的講述如何 拆開組合,而是將直接講述 拆開組合 的結果。

Description

給定一個 \(n \times m\) 的矩陣,第 \(i\) 行第 \(j\) 列的數為 \(a[i][j]\)

求一個矩形環使得環裡的數之和最大。

矩形環定義為一個 \(n \times w_1\) 的矩陣,其中 \(3 \le w_1 \le m\),然後在中間選取一個 \((n-2) \times w_2\) 的矩陣挖掉,第一行和最後一行要保留,其中 \(1 \le w_2 \le (m-2)\),且挖掉這個矩陣之後上下左右都要有保留的部分。

Step 1:\(n \le 10\)\(m \le 1000\)
Step 2:\(n \le 10\)\(m \le 10^5\)

Solution for Step 1

一個矩陣環即為:

---+++++--
---+--++--
---+--++--
---+++++--

拆開 結果如下所示:

---+ ++ ++--
---+ -- ++--
---+ -- ++--
---+ ++ ++--

我們還是列舉中間的 \([l,r]\),然後左右算最大子段和。

\(\mathcal O(n^2)\),期望得分 \(50\)

Solution for Step 2

重新 拆開

+ ++ ++
+ -- ++
+ -- ++
+ ++ ++

然後 組合 為三部分:

  1. 左的單獨塊。
  2. 左 + 中的 C 形塊。
  3. 左 + 中 + 右的矩形環。

具體細節請讀者自行完善,可以做到 \(\mathcal O(m)\)(輸入省略)。