0
0

More than 1 year has passed since last update.

paizaラーニング レベルアップ問題集 二分探索メニュー JavaScript パイプを切り出そう

Last updated at Posted at 2023-01-18

パイプを切り出そう (paizaランク A 相当)

解説

解き方を思いつくところが難しかったです。

解き方の流れ

切り出すパイプの長さを決める(長さの範囲の両端の真ん中mid)
→決めた長さで、A_1からA_nまで順に切り出し、本数を合計する
→切り出した本数の合計がk本切り出せているか判定する
→その判定により両端の範囲を狭める
→求められる誤差まで繰り返す。

解き方を思いつくまで

ある長さで、n本のパイプ1本1本から切り出す。切り出した本数がわかる。それがk本に満たないなら、もっと短い長さにする。k本を超えるなら、もっと長くできる。

できるだけ長くしたいので、k本切り出せるか切り出せないかの境目の長さが知りたい。手間がかかりそうなので、速い処理ができないか。二分探索が使えそう。

長さxのパイプをk本切り出せるなら、それ以下の長さのy(数式で表すとy<=x)についても、もちろん切り出せる。なので、yについては考えなくて良いので、範囲を一気に狭められる。

反対に、長さxのパイプをk本切り出せなかったら、それ以上の長さのz(数式で表すとx<=z)についても、もちろん切り出せない。なので、zについては考えなくて良いので、これも範囲を一気に狭められる。

切り出す長さを短くするほどたくさん切り出せる。長くするほど切り出す数が少なくなる。
こういった単調な性質があるので、パイプの長さで二分探索ができる。

方針:二分探索する

この問題について、パイプの長さの範囲は1 ≦ A_i ≦ 10,000。なので、この範囲を含む(left=)0 ≦ A_i ≦ 10,001(=right)において、二分探索をします。

両端の真ん中の長さmid=(rigth+left)/2のパイプをk本切り出せるかチェックします。

k本切り出せるかチェックの方法は、それぞれのパイプからmidの長さのパイプを切り出し、それぞれの本数を合計した本数がkより小さいか(または大きいか)で、できます。

i番目のパイプから切り出した長さmidのパイプの本数は、A_iをmidで割った商で求められます。これをn本について計算し、合計本数を求めます。
この合計本数がk本に到達しているか判定します。

k本に到達していないなら、切り出すパイプの本数を増やさないといけないので、切り出すパイプの長さを短くします。これより長い長さはk本に到達しないので調べる必要はありません。よって、調べた長さmidを、次から調べる範囲の右端rightにします。

反対に、k本に到達していたら、パイプの長さを長くできます。これより短い長さは調べなくて良いので、midを左端leftにできます。

この流れを、絶対誤差が 10^-6 (0.000001) 以下になるまで繰り返しますが、本条件では50回繰り返せば満たされます。

解答例(Python3とC++の場合参考)

方針では 50 回としていたところを、万全を期すために 100 回にしています。
長さ x のパイプから切り出せる長さ mid のパイプの本数は Math.trunc(a / mid) 本になります。

const fs = require("fs");
const input = fs.readFileSync("/dev/stdin", "utf-8").trim();
const lines = input.split("\n");

const [n, k] = lines[0].split(" ").map(Number);
const A = lines[1].split(" ").map(Number);

//切り出すパイプの長さを二分探索する
let [left, right] = [0, 10001];//探索するパイプの長さの範囲の両端
for (let i = 0; i < 100; i++) { 
/*相対誤差または絶対誤差が 10^-6 (0.000001) 以下であれば正解なので、
50回も繰り返せばよいが、万全を期すため、100回繰り返す */
  //長さmidのパイプを合計num_of_pipes本切り出す
  let mid = (left + right) / 2;//範囲の両端の真ん中の長さ
  let num_of_pipes = 0;//合計何本切り出せたか
  //それぞれのパイプの長さについて切り出していく
  for (let a of A) { 
    num_of_pipes += Math.trunc(a / mid);
    /* a/midで切り分け、
    長さmidに満たない切れ端はいらないのでMath.truncで切り捨てる
    求めた本数Math.trunc(a / mid)を、
    合計本数num_of_pipesに加えていく */
  }
  //求まった合計本数num_of_pipesが、k本切り出せたかチェックする
  if (num_of_pipes < k) { //k本に満たない=長さをもっと短くして本数を増やす
    right = mid;//midより長い長さはもちろん本数が足りないので、範囲の長い方(右端)をmidまで狭める
  } else { //k本切り出せた=長さをもっと長くできる=本数減らせる
    left = mid;//midより短いところはもちろんk本切り出せるので、短い方(左端)の範囲をmidまで狭める
  }
  
}
//100回探索して、切り出せるか切り出せないかのパイプの長さの境目
console.log(left);
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0