問題
荷物が $N$ 個、到着順に並んでいて、$i$ 番目の重さは $a_i$。荷物を到着順のまま連続する区間に分け、各区間を 1 便で運ぶ。重さの合計が $W$ の便のコストは
$$C+W^3$$
($C$ は 1 便ごとの固定費、$W^3$ は積載量に対して急に増える費用)。全便のコストの合計の最小値を求めよ。
入力形式
N C
a_1 a_2 ... a_N
制約
| # | 条件 |
|---|---|
| 1 | $1\le N\le2\times10^5$ |
| 2 | $0\le C\le10^{12}$ |
| 3 | $1\le a_i\le100$ |
| 4 | 入力はすべて整数 |
入出力例
入力例1
5 10
1 2 3 2 1出力例1
95(1 個ずつ 5 便に分けて $5\cdot10+1+8+27+8+1=95$。$[1,2],[3],[2,1]$ の 3 便は $30+81=111$ で負ける。全 16 通りの区間分けの全探索で確認)
入力例2
3 37
1 1 2出力例2
90($[1,1],[2]$ で $37+8+37+8=90$。「最適な切れ目を前から順に進める 2 ポインタ」は 101 を出す最小の例)
入力例3
3 18
2 1 1出力例3
52($[2],[1,1]$ で $18+8+18+8=52$。「直前の最適な切れ目と 1 つ前だけ見る」近道は 64 を出す最小の例)
背景と出典
$w(i,j)=f(S_j-S_i)$($S$ は累積和、$f$ は凸)は Monge 性 $w(i,j)+w(i',j')\le w(i,j')+w(i',j)$($i\le i'\le j\le j'$)を満たし、$dp[j]$ の最適な $i$ は $j$ について単調になる(決定単調性)。$dp$ が同じ配列を参照する形は「1D/1D」「オンライン」の DP と呼ばれ、Galil–Park(1990 年ごろ)の整理や、候補をキューに持って境界を二分探索する方法が広く知られている、と私は理解している(原典は本セッションでは未確認)。$O(N)$ で解く LARSCH 法(Larmore–Schieber 1991)もあるが、本セッションでは実装・検証していない。
$f(W)=W^2$ なら Convex Hull Trick で済む($-2S_iS_j$ が $S_j$ の 1 次式になる)。3 乗では $S_j$ について 2 次の項が残るので直線の下側包絡にならず、CHT が使えない。だから「コストの形に依存しない」Monge の道具の出番になる。
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
$dp[j]=\min_{0\le i<j}\bigl(dp[i]+C+(S_j-S_i)^3\bigr)$。素直には $O(N^2)$。Day169-Q2 の SMAWK や Day168-Q3 の分割統治は「行列の全要素が最初から分かっている」ことを前提にしていた。ここでは $dp[i]$ が分かるのは $i$ を計算した後。
ヒント2(アプローチ)
候補 $i<i'$ について、「$i'$ が $i$ 以下になる $j$ の集合」は $j$ のある位置から右全部(Monge 性から)。だから各候補が最良になる $j$ の範囲は左から右へ並ぶ区間になる。候補を「(番号, 最良になる最初の $j$)」の列としてキューに持ち、新しい候補 $j$ を末尾に入れるとき、末尾の候補を区間の先頭で負かしていれば丸ごと捨て、そうでなければ区間の中で境界を二分探索する。
ヒント3(誘導)
for j in 1..N:
先頭の候補の区間が j を過ぎていたら先頭を進める
dp[j] = val(先頭の候補, j)
while 末尾の候補 c の開始位置 b で val(j, b) <= val(c, b): 末尾を捨てる
末尾の候補 last の区間 [max(b, j+1), N] で val(j, m) <= val(last, m) となる最小の m を二分探索
m <= N なら (j, m) を末尾に追加模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
def main():
data = sys.stdin.buffer.read().split()
n, c = int(data[0]), int(data[1])
S = [0] * (n + 1)
for i in range(n):
S[i + 1] = S[i] + int(data[2 + i])
dp = [0] * (n + 1)
def val(i, j):
# 区間 (i, j] を 1 つの便にしたときの、j までの合計コスト
w = S[j] - S[i]
return dp[i] + c + w * w * w
# cand[k] は候補 i、beg[k] はその候補が最良になる最初の j(beg は増加列)
cand = [0]
beg = [1]
head = 0
for j in range(1, n + 1):
while head + 1 < len(cand) and beg[head + 1] <= j:
head += 1
dp[j] = val(cand[head], j)
if j == n:
break
# 新しい候補 i=j を末尾に入れる。末尾の候補より beg で既に良いなら丸ごと消す
while len(cand) > head + 1 and val(j, beg[-1]) <= val(cand[-1], beg[-1]):
cand.pop(); beg.pop()
last = cand[-1]
lo = max(beg[-1], j + 1)
hi = n + 1 # [lo, hi) で「j が last 以下」になる最初の位置を探す
while lo < hi:
mid = (lo + hi) // 2
if val(j, mid) <= val(last, mid):
hi = mid
else:
lo = mid + 1
if lo <= n:
cand.append(j); beg.append(lo)
print(dp[n])
main()
計算量: 各候補は 1 回入って 1 回出る。二分探索が 1 候補あたり $O(\log N)$ で全体 $O(N\log N)$。$N=2\times10^5$ で 1.49〜2.05 s(入力 3 種、Python 3.14.7、M1 Mac、実測)。答えは最大で約 $2\times10^5\cdot10^{12}+(2\times10^7)^3\approx8\times10^{21}$ になり得て 64 ビットを超える。Python の整数はそのまま扱えるが、C++ なら __int128 が要る。
Step-by-Step 解説
$f$ が凸なら、$x\le y$、$d\ge0$ で $f(x+d)-f(x)\le f(y+d)-f(y)$。$x=S_{i'}-S_i$… と置くと $w(i,j)+w(i',j')\le w(i,j')+w(i',j)$ が出る。$f(W)=W^3$ は $W\ge0$ で凸。
Monge 性を $i<i'$、$j<j'$ に使うと、$dp[i']+w(i',j)\le dp[i]+w(i,j)$ なら $j'$ でも $i'$ が勝つ。だから候補の勝つ範囲は区間で、後の候補ほど右にある。キューの末尾を捨てる条件と二分探索の単調性は、この 1 行から出る。
Day168-Q3 の分割統治は $j$ の中央から計算するが、中央の $dp[mid]$ を計算するには $dp[i]$($i<mid$)がすべて確定している必要がある。オンラインでは成り立たない。直すには、$[l,m)$ を確定させてから $[l,m)\to[m,r)$ の寄与を分割統治で配る CDQ 分割統治にする($O(N\log^2N)$、他案の表)。
最適な $i$ は単調だが、$i$ についての関数 $dp[i]+w(i,j)$ は単峰とは限らない。「次の候補が悪くならない間だけ進める」2 ポインタは谷の手前で止まる。ランダム 500 件で 60 件誤答(実測)、最小の例が入力例 2。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 全 $i$ を見る素直な DP | $O(N^2)$ | 検算用のみ | ランダム 500 件の正解に使用 |
| CHT(直線の下側包絡) | $O(N)$ | 不採用 | コストが $S_j$ の 1 次式にならない(3 乗) |
| 2 ポインタ(単峰を仮定) | $O(N)$ | 不採用 | 60/500 誤答 |
| 直前の最適と $j-1$ だけ見る | $O(N)$ | 不採用 | 151/500 誤答 |
| CDQ 分割統治+monotone minima | $O(N\log^2N)$ | 次点 | 500 件一致、$N=2\times10^5$ で 3.32 s(本解の約 2 倍) |
| 候補キュー+二分探索(本解) | $O(N\log N)$ | 採用 | 500 件一致、1.49〜2.05 s、実装が短い |
| LARSCH | $O(N)$ | 不採用 | 実装が長く、本セッションで検証していない。$N=10^6$ 以上で定数倍が効くなら検討 |
前提が変わったら:
- $f(W)=W^2$ なら CHT で $O(N)$
- $f$ が凹(大口割引)なら Monge の不等式が逆向きになり、本解の「後の候補が勝ち続ける」が成り立たない。候補キューの向きを反転させた別の手法(スタック+二分探索)が要る
- 便の数がちょうど $K$ なら、$K$ 層の 2D/1D になり分割統治最適化(Day168-Q3)か Alien DP
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 分割統治最適化をそのまま使う | まだ計算していない $dp[i]$ を読む | CDQ にするか、候補キュー |
| 2 ポインタで最適な $i$ を進める | 決定単調性と単峰性を混同 | 二分探索で境界を求める(60/500 誤答) |
二分探索の左端を beg[-1] のままにする | $j$ 以下の位置で $val(j,\cdot)$ を評価してしまう | max(beg[-1], j+1) |
| CHT を当てる | $W^2$ の問題と同じだと思う | 3 乗は 1 次式に分解できない |
C++ で long long | $(2\times10^7)^3=8\times10^{21}$ | __int128 |
次のステップ
- 発展1: 同じ問題を LARSCH で $O(N)$ にし、$N=10^6$ で本解と速さを比べる
- 発展2: コストが $C+\sqrt W$(凹)のときの 1D/1D を、スタック版で解く
- 発展3: 便の数をちょうど $K$ にした版を Alien DP(Lagrange 緩和)で解き、$C$ を二分探索する
検証ログ(本セッションで実行した数値)
- 素直な DP との照合(実測): $N\le40$、$a_i\le3/10/100$、$C$ を 3 段階に振ったランダム 500 件で本解・CDQ とも不一致 0
- 誤った方法(実測): 同じ 500 件で 2 ポインタ 60 件、直前の最適と $j-1$ だけ見る方法 151 件誤答。最小の反例(20 万件のランダム探索)が入力例 2・3
- 大入力の一致(実測): $N=2\times10^5$ の 3 種と $N=10^5$ の 1 種で本解と CDQ の答えが一致(例: 1908029859298045、3779848720、51122579703、955582065011705)
- 実行時間(実測): 本解は $N=2\times10^5$ で 1.66 s($C=10^{12}$)、1.49 s($a_i=1$)、2.05 s($C=0$)、$N=10^5$ で 0.74 s。CDQ は $N=2\times10^5$ で 3.32 s、$N=10^5$ で 1.68 s
- 入力例(実測): 例 1
95、例 290、例 352 - 扱っていない範囲: LARSCH の実装、凹コスト版、Galil–Park・Larmore–Schieber の原典