0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

paizaラーニング レベルアップ問題集 二分探索メニュー応用編 JavaScript ハイパーインフレーション

Last updated at Posted at 2023-01-27

ハイパーインフレーション (paizaランク B 相当)

解き方

大きな流れをまとめる。

問題文把握。

条件から、x日目について二分探索。

探索では、各食品の安い方の商品で、総額を求め、K以下になるか判定する。

問題文を正しく理解するところから。

N 種類の食品をそれぞれ 1 つずつ買い
それぞれの食品には 2 種類の商品があり

つまり、N種類の食品、それぞれの食品について、2種類の中から、1種類選ぶ。
例えば、3種類の食品、りんご、みかん、バナナがあったら、
それぞれの食品について、2種類あるので、りんご(サンふじ、王林)、みかん(有田、三ヶ日)、バナナ(フィリピン、エクアドル)、となり、その中から1種類だけ選ぶ。りんご(サンふじ)、みかん(有田)、バナナ(エクアドル)のように。

i 番目の食品の x (x ≧ 1) 日目の価格

N種類のうち、i番目(i種類目)の食品の、x日目の価格、ということ。
例えば、3種類のうち2番目のみかんの、3日目の価格、ということ。

それぞれ A_i x + B_i 円, C_i x + D_i 円で

A_i x + B_i 円, C_i x + D_i 円のA_i x、C_i xが何を意味しているのか、はじめよくわからなかった。
試行錯誤したが、どうやらA_i x、C_i xは掛け算、すなわち、A_i * x + B_i 円, C_i * x + D_i 円を表しているようだ。

なので、具体例を挙げると、3種類のうち2番目のみかんの入力が入力が[A_2, B_2, C_2, D_2,] = [3 200 2 300]のとき、3日目の価格はx=3なので、

有田みかんA_i * x + B_i 円, 三ヶ日みかんC_i * x + D_i 円
=有田みかん3 * 3 + 200 円, 三ヶ日みかん2 * 3 + 300 円
=有田みかん209 円, 三ヶ日みかん306 円

となる。

合計 K 円以下の価格で買いそろえる

ので、2種類の商品のうち、安い方を選ぶ。上の具体例なら、有田みかん209 円, 三ヶ日みかん306 円なので、有田みかん209 円を選ぶ。


入力の条件を見ると、

1 ≦ N ≦ 200,000 = 2 × 10^5
・ 2 ≦ K ≦ 1,000,000,000,000 = 10^12
・ 1 ≦ A_i, B_i, C_i, D_i ≦ 1,000,000 = 10^6

と、とても大きな数なので、ふつうにforループをしたら、計算が終わらなそう。

ハイパーインフレーションにより、その価格は常に上昇中です。

xが増えれば金額の増えるので、二分探索が使えそうです。


x日目かで二分探索してみる。

x日目の範囲は、1<=x<=10^6で探索する。

理由は、以下。
最小値は、1日目がスタート。
一番日数がかかりそうなのは、所持金Kが最大10^12で、商品が最安の時、つまり、N=1でA,B,C,Dが全て1で2種類の商品価格がどちらも2円の時。
細かく計算すれば、
2円*(1+2+...+x)日=10^12
1からxまでの和を求める公式より
2*1/2x(x+1)=10^12
x(x+1)=10^12
x^2+x=10^12
を満たすxだが、

ざっくり、xが十分大きい時、x^2とxなら、xは無視できるので、
x^2=10^12
x=10^6
よって、xの範囲の最大値は10^6とする。

したがって、xの範囲は[left,right]=[1,10^6]。

x=mid=(left+right)/2として、
N種類の食品から、安い方を足し合わせた合計金額が、K以下か判定。
これを、right-left<=1まで繰り返す。

実装例

解き方に沿って解いた。

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);

//何日目か、で二分探索してみる
let [left, right] = [1, 1000000];//xの範囲
while (right - left > 1) { //何日か、は整数なので1以下になれば良い

  const mid = Math.trunc((left + right) / 2);
  //mid日目の総額
  let price = 0;//総額
  for (let i = 1; i <= N; i++) {
    const [A, B, C, D] = lines[i].split(" ").map(Number);
    price += Math.min(A * mid + B, C * mid + D);//安い方選ぶ
  }

  //総額がK以下か
  if (price <= K) { //総額priceがK以下なら、mid日未満もK以下で買える
    left = mid;
  } else { //総額priceがK超えたら、mid日以上も買えない
    right = mid;
  }
}
console.log(left);

実装例(Python3の場合参考)

合計金額が手持ちのお金K円より大きいかチェックする、関数checkを定義した。

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 ABCD = lines.slice(1).map(line => line.split(" ").map(Number));

//合計金額が手持ちのお金K円より大きかったらtrue,以下だったらfalse
const check = (x) => {
    let sum = 0;
    for (let abcd of ABCD) {
      const [a, b, c, d] = abcd;
        sum += Math.min(a * x + b, c * x + d);
    }
    if(sum <= K) {
      return 0;
    } else {
      return 1;
    }
};

//leftは常に条件を満たす,rightは常に条件を満たさない
let [left, right] = [1, 1 << 47];//xの範囲
while (right - left > 1) { //何日か、は整数なので1以下になれば良い

  const mid = Math.trunc((left + right) / 2);
  
  //総額がK以下かチェック
  if (check(mid)) { 
    right = mid;
  } else { 
    left = mid;
  }
}
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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?