C - 丸太の分割 / Splitting Logs 解説 by admin
GPT 5.2 High(貪欲の正当性の証明なし)概要
長さ配列 \(A_1,\ldots,A_N\) を順番を保ったまま \(K+1\) 個に分割するとき、「各断片長の最大値 \(M\)」を最小化する問題です。
答えとなる \(M\) を二分探索し、ある上限 \(x\) で分割可能かを貪欲に判定します。
考察
重要な気づき
「最大断片長を \(x\) 以下にできるか?」という判定問題を考えると、次が成り立ちます。
- もし \(x\) が十分大きければ分割は可能
- \(x\) が小さすぎると不可能
- つまり「可能/不可能」は \(x\) に対して単調(\(x\) が大きいほど可能になりやすい)
この単調性があるので、最小の \(x\)(=答え)を二分探索で求められます。
素朴案が難しい理由
切断箇所は \(N-1\) 個あり、そこから \(K\) 個選ぶので組み合わせは \(\binom{N-1}{K}\) 通りです。
\(N \le 2\times 10^5\) では全探索は到底不可能です。
判定をどう作るか(貪欲)
「最大断片長を \(x\) 以下にしたい」とき、左から順に
- 今の断片に追加しても \(x\) を超えないなら追加
- 超えるならそこで切って新しい断片を開始
とするのが最も断片数を少なくする貪欲です。
この方法でできた断片数が \(K+1\) 以下なら、\(x\) で分割可能です。
※問題は「ちょうど \(K\) 回切って \(K+1\) 断片」ですが、断片数が少なめ(\(\le K+1\))でもOKです。長さはすべて正なので、断片をさらに細かく分割しても各断片長は増えません(最大値も悪化しない)ため、最終的にちょうど \(K+1\) 個に調整できます。
具体例:\(A=[3,1,4,1,5],\ K=2\)(3断片)で \(x=5\) を判定
貪欲に詰めると
- \(3+1=4\)(まだOK)、次の \(4\) を足すと \(8\) でNG → ここで切る(断片長 4)
- 次は \(4+1=5\)(OK)、次の \(5\) を足すと \(10\) でNG → 切る(断片長 5)
- 最後は \(5\)(断片長 5)
断片数は 3 個で \(K+1=3\) 以下なので可能、となります。
アルゴリズム
- 答え \(M\) の探索範囲を決める
- 下限 \(lo = \max(A_i)\)(どんな分け方でも最大断片長は少なくとも最大要素以上)
- 上限 \(hi = \sum A_i\)(切らなければこの長さ)
- 二分探索で \(x\) を試す
feasible(x):最大断片長を \(x\) 以下にして断片数を \(K+1\) 以下にできるか
feasible(mid)が真なら上限を下げ、偽なら下限を上げる- \(lo=hi\) になった値が最小の達成可能な最大断片長(答え)
判定 feasible(x)(貪欲):
- 断片数 cnt=1、現在の断片和 s=0
- 左から順に、s+a <= x なら s+=a
- そうでなければ新断片開始:cnt += 1, s = a
- 途中で cnt > K+1 になったら不可能(False)
- 最後まで行けたら可能(True)
計算量
- 時間計算量: \(O(N \log(\sum A_i))\)
(二分探索が \(\log(\sum A_i)\) 回、各回の判定が \(O(N)\)) - 空間計算量: \(O(N)\)
(配列 \(A\) を保持)
実装のポイント
二分探索の下限は必ず
max(A)にする(これより小さい \(x\) は絶対不可能)。判定は「断片数が \(K+1\) 以下ならOK」とするのがコツ(ちょうどでなくてよい理由は考察の通り)。
\(\sum A_i\) は最大で \(2\times 10^5 \times 10^9 = 2\times 10^{14}\) なので、Python 以外では 64bit 整数が必須です。
ソースコード
import sys
def main():
input = sys.stdin.buffer.readline
N, K = map(int, input().split())
A = list(map(int, input().split()))
need_segments = K + 1
lo = max(A)
hi = sum(A)
def feasible(x: int) -> bool:
cnt = 1
s = 0
for a in A:
if s + a <= x:
s += a
else:
cnt += 1
s = a
if cnt > need_segments:
return False
return True
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid):
hi = mid
else:
lo = mid + 1
print(lo)
if __name__ == "__main__":
main()
この解説は gpt-5.2-high によって生成されました。
投稿日時:
最終更新: