Day 050-Q5 — Concave DP最適化・SMAWK(四辺形不等式 + 分割統治 $O(KN \log N)$)

2026-06-03 赤色 Master / Phase 8+ ★★★★★★★★★ 四辺形不等式 / 分割統治DP最適化 / SMAWK

問題

長さ $N$ の数列を ちょうど $K$ 個の連続する非空部分列に分割する。各部分列 $[l, r]$($r-l+1$ 個の要素を含む)のコストを $\text{cost}(l,r) = (r-l+1)^2$(長さの2乗)とする。

$K$ 個の部分列の コスト総和を最小化 せよ。(数列の要素自体はコストに影響しない。)

制約

$1 \le K \le N \le 10^6$
時間制限: 3秒

入出力例

入力例 1

6 3
1 2 3 4 5 6

出力例 1

12

入力例 2

8 3
1 1 1 1 1 1 1 1

出力例 2

22

例1: $N=6, K=3$ → 均等分割 $[1,2],[3,4],[5,6]$: $4+4+4=12$。例2: $N=8, K=3$ → $[1..3],[4..5],[6..8]$: $9+4+9=22$。

概念図: 四辺形不等式と最適分割点の単調性

dp[k][i] = 最初のi要素をk個に分けた最小コスト 遷移: dp[k][i] = min_{j<i} dp[k-1][j] + (i-j)² 最適分割点 opt[i] の単調性(四辺形不等式 → opt は単調非減少) i (現在の位置) opt[i] opt[i] が単調 → 分割統治 O(N log N) 分割統治の流れ compute(l, r, opt_lo, opt_hi) mid = (l+r)//2 opt[mid] を [opt_lo, opt_hi] で探索 左: compute(l, mid-1, opt_lo, opt[mid]) 右: compute(mid+1, r, opt[mid], opt_hi) 各レベルで合計 O(N) → O(N log N) per k

ヒント(段階的開示)

ヒント1: 方向性
DP で $dp[k][i]$ = 最初の $i$ 要素を $k$ グループに分けた最小コストとする。ナイーブ $O(KN^2)$ を最適化できないか。
ヒント2: アプローチ
$\text{cost}(j, i) = (i-j)^2$ は 四辺形不等式(QI: $w(a,c)+w(b,d) \le w(a,d)+w(b,c)$ for $a \le b \le c \le d$)を満たす。これにより最適分割点 $\text{opt}(i)$ が $i$ について単調非減少 → 分割統治 DP 最適化で各 $k$ のレイヤーを $O(N \log N)$ で処理。
ヒント3: 四辺形不等式の証明と実装骨格
$w(a,c)+w(b,d) \le w(a,d)+w(b,c)$($a \le b \le c \le d$):
$(c-a)^2+(d-b)^2 \le (d-a)^2+(c-b)^2$
展開: $c^2-2ac+a^2+d^2-2bd+b^2 \le d^2-2ad+a^2+c^2-2bc+b^2$
整理: $-2ac-2bd \le -2ad-2bc$ → $2(ad-ac-bd+bc) = 2(a-b)(d-c) \ge 0$ ✓($a \le b, c \le d$ より)
def solve_layer(k, prev_dp, N):
    new_dp = [float('inf')] * (N+1)
    stack = [(k, N, k-1, N-1)]
    while stack:
        l, r, opt_lo, opt_hi = stack.pop()
        if l > r: continue
        mid = (l+r)//2
        best, bopt = float('inf'), opt_lo
        for j in range(opt_lo, min(opt_hi, mid-1)+1):
            c = prev_dp[j] + (mid-j)**2
            if c < best: best=c; bopt=j
        new_dp[mid] = best
        stack.append((l, mid-1, opt_lo, bopt))
        stack.append((mid+1, r, bopt, opt_hi))
    return new_dp

模範解答 (Python)

import sys
input = sys.stdin.readline

def solve():
    N, K = map(int, input().split())
    _ = input()  # 数列は使わない

    INF = float('inf')
    dp = [INF] * (N + 1)
    dp[0] = 0

    for k in range(1, K + 1):
        new_dp = [INF] * (N + 1)
        # 反復的な分割統治最適化
        stack = [(k, N, k - 1, N - 1)]
        while stack:
            l, r, opt_lo, opt_hi = stack.pop()
            if l > r:
                continue
            mid = (l + r) // 2
            best = INF
            best_opt = opt_lo
            hi = min(opt_hi, mid - 1)
            for j in range(opt_lo, hi + 1):
                length = mid - j
                c = dp[j] + length * length
                if c < best:
                    best = c
                    best_opt = j
            new_dp[mid] = best
            stack.append((l, mid - 1, opt_lo, best_opt))
            stack.append((mid + 1, r, best_opt, opt_hi))
        dp = new_dp

    print(dp[N])

solve()

Step-by-Step 解説

1DP の定式化
$dp[k][i]$ = 最初の $i$ 要素を $k$ グループに分けた最小コスト。遷移: $dp[k][i] = \min_{j < i}(dp[k-1][j] + (i-j)^2)$。ナイーブ $O(KN^2)$。
2四辺形不等式の確認
$w(j,i) = (i-j)^2$ が QI を満たすことを証明: $w(a,c)+w(b,d) \le w(a,d)+w(b,c)$ は $(a-b)(d-c) \ge 0$ に帰着($a \le b \le c \le d$ より成立)。
3最適分割点の単調性
QI → $\text{opt}(i)$ は $i$ について単調非減少。つまり $\text{opt}(l) \le \text{opt}(mid) \le \text{opt}(r)$($l \le mid \le r$)。
4分割統治最適化
$mid = (l+r)/2$ に対して $\text{opt}(mid)$ を $[\text{opt\_lo}, \text{opt\_hi}]$ で線形探索。左半分は $[\text{opt\_lo}, \text{opt}(mid)]$、右半分は $[\text{opt}(mid), \text{opt\_hi}]$ で再帰。各レベルで合計 $O(N)$ → 全体 $O(N \log N)$ per layer。
5均等分割の性質
$\sum l_i^2$($\sum l_i = N$, 各 $l_i \ge 1$)の最小化は、長さをなるべく均等にすること(凸関数の性質)。$\lfloor N/K \rfloor$ と $\lceil N/K \rceil$ の組み合わせが最適。

計算量

各 $k$ のレイヤー: $O(N \log N)$
全体: $O(KN \log N)$
SMAWK による改善: $O(KN)$(各レイヤー $O(N)$)
空間: $O(N)$(dp 配列 1 本を更新)

よくあるミス

ミス原因正しい書き方
opt の上限を mid にしてしまう$j < i$ が必要min(opt_hi, mid - 1) が正しい上限
K 層目のコスト計算で dp が未更新前層の dp を上書きnew_dp を別配列で作り完成後に dp = new_dp
スタックオーバーフロー$N=10^6$ での深い再帰反復 DFS(明示的スタック)で実装
四辺形不等式の確認不足単調性が成立しないコスト関数に誤用必ず $w(a,c)+w(b,d) \le w(a,d)+w(b,c)$ を証明

次のステップ

  • 発展問題: コスト関数が累積和依存($w(l,r) = (A_r - A_{l-1})^2$)の場合への拡張
  • 関連: Knuth の最適化(区間 DP の特殊形)との比較
  • 応用: SMAWK アルゴリズムを実装して $O(KN)$ に改善する

自己評価