公式

D - 花壇の区間分け / Dividing the Flower Bed into Sections 解説 by admin

GPT 5.2 High

概要

花の高さ列 \(A_1,\ldots,A_N\) を順番を保ったままちょうど \(K\) 個の連続区間に分け、各区間の「最大値−最小値」の合計(総手間)を最小化する問題です。

考察

  • 区間 \([l,r]\)(連続部分)の手間は、その区間の最大値と最小値が分かれば \( \max(A_l..A_r) - \min(A_l..A_r)\) と一発で計算できます。
  • したがって本質は「どこで区切るか」(境界 \(r_1,\ldots,r_{K-1}\) をどこに置くか)の最適化です。

素朴に「区切り方を全探索」すると、区切り位置の候補が膨大(組合せ爆発)で現実的ではありません。
また、DPをしても「区間の手間」を毎回その場で最大・最小を取り直すと、遷移ごとに \(O(N)\) かかって全体が \(O(KN^3)\) になり、無駄が大きくなります。

そこで、 1. すべての区間 \([l,r]\) の手間 \(cost[l][r]\) を前計算しておく(これで遷移時は \(O(1)\) 参照) 2. 「先頭から i 本を k 区間で分けた最小値」という典型的な区間DPを使う

という方針にします。

アルゴリズム

1. 区間コストの前計算

\(cost[l][r] = \max(A_l..A_r) - \min(A_l..A_r)\) を全て求めます(\(0\)-indexed とします)。

\(l\) について右端 \(r\) を伸ばしながら最小値・最大値を更新すると、 - 初期:\(mn=mx=A[l]\) - \(r=l,l+1,\ldots,N-1\) と進めて、\(mn=\min(mn,A[r])\), \(mx=\max(mx,A[r])\) - \(cost[l][r]=mx-mn\)

\(O(N^2)\) で全区間を埋められます。

2. DP(区間分割)

\(dp[k][i]\) を「先頭から \(i\) 本(\(A_0..A_{i-1}\))をちょうど \(k\) 区間に分けたときの最小総手間」とします。
すると最後の区間を「\(j\) 本目までで区切って、残り \(A_j..A_{i-1}\) を最後の区間にする」と考えられます。

遷移は [ dp[k][i] = \min_{j} \left( dp[k-1][j] + cost[j][i-1] \right) ] ただし区間は空にできないので、\(k-1\) 区間で \(j\) 本を作れる必要があり、コードでは \(j \in [k-1,\, i-1]\) を走らせています。

初期条件は - \(dp[0][0]=0\)(0本を0区間で分ける手間は0) - それ以外は不可能なので \(\infty\)

最終解は \(dp[K][N]\) です。

※実装ではメモリ節約のため \(dp\) を2行(前の \(k-1\) と現在の \(k\))だけ持つローリング配列にしています。

計算量

  • 時間計算量: 前計算 \(O(N^2)\) + DP \(O(KN^2)\) なので全体で \(O(N^2 + KN^2)=O(KN^2)\)
  • 空間計算量: \(cost\)\(O(N^2)\)、DP配列が \(O(N)\) なので \(O(N^2)\)

(制約 \(N \le 200\) なので十分間に合います。)

実装のポイント

  • 添字のずれに注意:DPは「先頭から \(i\) 本」を扱うため、最後の区間コスト参照は cost[j][i-1] になります。

  • 区間を空にしないために、遷移の \(j\)range(k-1, i)\(j \le i-1\))とします。

  • \(\infty\) は十分大きい値(例:10**18)を使い、最小化で更新します。

    ソースコード

import sys

def main():
    it = iter(sys.stdin.read().split())
    N = int(next(it))
    K = int(next(it))
    A = [int(next(it)) for _ in range(N)]

    # cost[l][r] = max(A[l..r]) - min(A[l..r])
    cost = [[0] * N for _ in range(N)]
    for l in range(N):
        mn = mx = A[l]
        for r in range(l, N):
            if A[r] < mn:
                mn = A[r]
            if A[r] > mx:
                mx = A[r]
            cost[l][r] = mx - mn

    INF = 10**18
    dp_prev = [INF] * (N + 1)
    dp_prev[0] = 0

    for k in range(1, K + 1):
        dp = [INF] * (N + 1)
        for i in range(1, N + 1):
            best = INF
            for j in range(k - 1, i):
                v = dp_prev[j] + cost[j][i - 1]
                if v < best:
                    best = v
            dp[i] = best
        dp_prev = dp

    print(dp_prev[N])

if __name__ == "__main__":
    main()

この解説は gpt-5.2-high によって生成されました。

投稿日時:
最終更新: