問題
$N$ 個の石があり,それぞれ重さ $w_1, w_2, \ldots, w_N$ を持つ。隣接する石を合体させるコストは「合体後の石の重さ」。これを最終的に $K$ 個の石 が残るまで合体させる最小総コストを求めよ。
区間 $[l, r]$ を 1 つにするコスト $C[l][r]$ を以下で定義する(0-indexed):
$$C[l][r] = \min_{l \le m < r}(C[l][m] + C[m+1][r]) + \sum_{i=l}^{r} w_i, \quad C[i][i] = 0$$$N$ 個の石を $K$ 個の連続区間に分割し,各区間の $C$ の和を最小化せよ。
制約
| パラメータ | 範囲 | 備考 |
|---|---|---|
| $N$ | $1 \le N \le 2000$ | 石の数 |
| $K$ | $1 \le K \le N$ | 残す石の数 |
| $w_i$ | $1 \le w_i \le 10^6$ | 重さ |
入出力例
入力例1
5 2
1 2 3 4 5
出力例1
15
入力例2
6 3
3 1 4 1 5 9
出力例2
18
概念図: 四辺形不等式と最適分割点の単調性
ヒント
ヒント1(方向性)
2 つの DP を組み合わせる:(1) 区間 DP $C[l][r]$ の前計算,(2) 分割 DP $dp[k][i]$ の計算。どちらにも四辺形不等式が適用できる。
ヒント2(アプローチ)
四辺形不等式: $w(a,c) + w(b,d) \le w(a,d) + w(b,c)$ ($a \le b \le c \le d$) が成立すると,最適分割点が単調になる(Knuth の定理)。
累積和コスト $C[l][r]$ は重みの前置和を加算する形なので四辺形不等式を満たす。→ $C[l][r]$ の計算が $O(N^2)$ に削減される。
ヒント3(ほぼ答え)
# 分割 DP を分割統治で O(N log N) に最適化
def rec(lo, hi, opt_lo, opt_hi):
if lo > hi: return
mid = (lo + hi) // 2
best = INF; best_m = opt_lo
for m in range(opt_lo, min(opt_hi, mid)+1):
if dp_prev[m] != INF:
val = dp_prev[m] + C[m][mid-1]
if val < best:
best = val; best_m = m
dp_cur[mid] = best
rec(lo, mid-1, opt_lo, best_m)
rec(mid+1, hi, best_m, opt_hi)
模範解答
import sys
input = sys.stdin.readline
def solve():
N, K = map(int, input().split())
w = list(map(int, input().split()))
S = [0] * (N+1)
for i in range(N): S[i+1] = S[i] + w[i]
INF = float('inf')
# C[i][j]: 区間 [i,j] を 1 つにする最小コスト (Knuth 最適化)
C = [[0]*N for _ in range(N)]
opt_C = [[0]*N for _ in range(N)]
for i in range(N): opt_C[i][i] = i
for length in range(2, N+1):
for i in range(N - length + 1):
j = i + length - 1
best = INF
lo = opt_C[i][j-1]
hi = opt_C[i+1][j] if i+1 <= j else j
for m in range(lo, hi+1):
val = C[i][m] + (C[m+1][j] if m+1 <= j else 0)
if val < best:
best = val; opt_C[i][j] = m
C[i][j] = best + (S[j+1] - S[i])
# 分割 DP
dp_prev = [INF] * (N+1)
dp_prev[0] = 0
for i in range(1, N+1):
dp_prev[i] = C[0][i-1]
for k in range(2, K+1):
dp_cur = [INF] * (N+1)
def rec(lo, hi, opt_lo, opt_hi):
if lo > hi: return
mid = (lo + hi) // 2
best = INF; best_m = opt_lo
for m in range(opt_lo, min(opt_hi, mid)+1):
if m >= k-1 and dp_prev[m] != INF:
val = dp_prev[m] + C[m][mid-1]
if val < best:
best = val; best_m = m
dp_cur[mid] = best
rec(lo, mid-1, opt_lo, best_m)
rec(mid+1, hi, best_m, opt_hi)
rec(k, N, k-1, N)
dp_prev = dp_cur
print(dp_prev[N])
solve()
Step-by-Step 解説
Step 1: 問題の DP 定式化
$C[i][j]$ = 区間 $[i, j]$ を 1 つにする最小コスト:$C[i][j] = \min_{m}(C[i][m] + C[m+1][j]) + S[j+1] - S[i]$
$dp[k][i]$ = $A[0, \ldots, i-1]$ を $k$ 個に分ける最小コスト:$dp[k][i] = \min_{m}(dp[k-1][m] + C[m][i-1])$
Step 2: 四辺形不等式の確認
$C[a][c] + C[b][d] \le C[a][d] + C[b][c]$ ($a \le b \le c \le d$) が帰納法で示せる → 最適分割点の単調性が成立。
Step 3: Knuth 最適化 ($C$ の計算を $O(N^2)$ に)
$opt\_C[i][j-1] \le opt\_C[i][j] \le opt\_C[i+1][j]$ を利用して探索範囲を絞る。全体の比較回数 $O(N^2)$。
Step 4: 分割 DP の分割統治最適化 ($O(N \log N)$ per layer)
rec(lo, hi, opt_lo, opt_hi) で二分して処理。各深さで合計 $O(N)$ 比較 × $O(\log N)$ 深さ = $O(N \log N)$ / layer。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| opt の境界ミス | 上界・下界の計算を誤る | 長さ昇順に処理して opt_C[i][j-1] と opt_C[i+1][j] を正しく参照 |
| 分割統治の境界外参照 | m < k-1 のケースを skip しない | if m >= k-1 and dp_prev[m] != INF: |
| 0/1-indexed 混在 | 配列定義とアクセスが不一致 | 全て 0-indexed に統一 |
次のステップ
- 発展問題: LARSSON 型ナップサック分割 DP(WQS 二分探索 + Knuth-Yao 二重最適化)
- ACL の Convex Hull Trick との組み合わせ(傾き単調 + 分割点単調の同時適用)
自己評価
理解度: / /
自分の回答:
気づき・メモ: