公式

D - ロープの分割 / Splitting a Rope 解説 by kyopro_friends


この問題は答えを二分探索することで解くことができます。

以下では、入力で与えられた \(K\) から \(1\) を引き、切る回数を \(K\) とします。

まずは次の判定問題を考えます。

問題1: \(K\) か所を選んで切断することで、長さの最大値を \(X\) 以下にできるか判定せよ。

この問題は次の問題と同値です。

問題2: 長さの最大値が \(X\) 以下になるように切断するとき、切断回数を \(K\) 回以下にできるか判定せよ。

少ない回数の切断で長さの最大値を \(X\) 以下にするためには「丸太を端から見て \(X\) を超える直前の箇所で切る」を繰り返すのが最適です。

証明 「丸太を端から見て $X$ を超える直前の箇所で切る」という方法を切り方 $P$ と呼び、切る箇所を $P_1,\dots,P_k$ とします。長さの最大値が $X$ 以下になる切り方のうち切断回数が最小のものを任意に1つ選び、切り方 $Q$ と呼び、この方法で切る箇所を $Q_1,\dots,Q_{k'}$ とします。最小性から $k\geq k'$ です。$P,Q$ を先頭から比較します。
  • 全ての $i=1,\dots,k'$ で$P_i=Q_i$ のとき
  • 切り方 $Q$ より $Q_{k'}$ から端までの長さは $X$ 以下なので、切り方 $P$ の定義から、切り方 $P$ においてもこの間で切ることはありません。よって $P=Q$ となります。
  • ある $i$ が存在して $P_i \neq Q_i$ となるとき
  • そのような最小の $i$ を取ると、切り方 $P$ の定義から、 $Q_i$ は $P_i$ より "手前" にあります。このとき、 $Q_i$ を $P_i$ に置き換えた切り方 $(Q_1,\dots,Q_{i-1},P_i,Q_{i+1},\dots)$ も、長さの最大値は $X$ 以下です。(切り方 $Q$ と長さが変わっている $2$ 箇所について、$Q_{i-1},P_i$ 間は $P_{i-1},P_{i}$ 間に等しく $X$ 以下であり、$P_i, Q_{i+1}$ 間は $Q_i,Q_{i+1}$ 間未満なので $X$ 以下です)。 よってこれを繰り返すことで $Q$ を $P$ に一致させることができ、 $P$ の切断回数が最小であることがわかりました。

よって、この判定問題は \(O(N)\) で解けることがわかりました。

元の問題は答えを二分探索することで、この判定問題に帰着できます。よって、\(O(N\log \sum A_i)\) で解くことができます。

判定問題を解く際、実装によっては \(X < \max A_i\) のケースに注意してください。

実装例 (C++)

#include<bits/stdc++.h>
using namespace std;

int main(){
  int n, k;
  cin >> n >> k;
  k--;
  vector<int>a(n);
  for(int i=0; i<n; i++) cin >> a[i];

  auto solve=[&](long long m){
    int cut = 0;
    long long crr = 0;
    for(int i=0; i<n; i++){
      if(crr + a[i] > m){
        cut++;
        crr = 0;
      }
      crr += a[i];
    }
    return cut <= k;
  };

  long long ng = *max_element(a.begin(), a.end()) - 1;
  long long ok = 1e15;
  while(ok - ng > 1){
    long long m = (ok + ng) / 2;
    if(solve(m)){
      ok = m;
    }else{
      ng = m;
    }
  }

  cout << ok << endl;
}

実装例 (Python)

N, K = map(int, input().split())
A = list(map(int, input().split()))
K -= 1

def solve(m):
  cut = 0
  crr = 0
  for a in A:
    if crr + a > m:
      cut += 1
      crr = 0
    crr += a
  return cut <= K

ng = max(A) - 1
ok = 10**15
while ok - ng > 1:
  m = (ok + ng) // 2
  if solve(m):
    ok = m
  else:
    ng = m

print(ok)

投稿日時:
最終更新: