競技プログラミング「AtCoder」をJavaScriptでやってみた その2

code

ABC207-A

問題「 3枚のカードA,B,Cから2枚選んでその和の最大は」

制約は3枚とも1~100までの整数。例1[3 4 5]なら4+5で9、例2[6 6 6]なら6+6で12になり、仕組みは簡単ですが、これをコードにすると?これも答えはいくつも考えられますが、今回はJavaScriptの”並べ替え”の技を使いましょう。それぞれを操作できる配列に取り出してから、sortを使い小さい順に並び変えます。あとは2番目と3番目を足せば完了です。

"use strict";
function main(input) {
  let items = input.trim().split(" ").map(Number);
  items.sort((a, b) => a - b);
  console.log(items[1] + items[2]);
}
main(require('fs').readFileSync('/dev/stdin', 'utf8'));

コードにするとこうなります。sortは配列にしか使えないので、まずinputを配列にしますが、いきなり色々とわからない記述が…trimはこのAtCoderで初めて見ました。「前後の余計な空白を取り除く」そうで、AtCoderでは出ないと思いますが、[   3 4 5   ]のような入力を[3 4 5]にしてくれるので、一応書いている人も多いみたいです。

続いてsplitは「指定した文字で区切って配列へ格納」これだけだとよくわからないですが、

let week = “Mon.Tue.Wed.Thu.Fri”;
let days = week.split(“.”);
console.log(days);
>> [“Mon”, “Tue”, “Wed”, “Thu”, “Fri”]

指定した「.」で区切ってますよね。今回の問題に当てはめればsplit(“ ”)はよ~く見ると半角スペースがありますので、[3 4 5]を半角スペースで区切り[3,4,5]にしています。最後のmap(Number)で要素全てを数値型に変換し、これをitemsとして処理を進めます。

sortはアロー関数で書いてますが、元は

items.sort(function(a, b) {
  return a - b;
});

であって、ここでそれぞれを比べています。3-4をして-1(0未満)なので3が前に来て3-5も3が前、4-5も負なので4が前に来て[3 4 5]となります。別の書き方で

array.sort(
  function(a,b){
    return (a < b ? -1 : 1);
  }
);

もありaがbより小さかったなら-1そうでなければ1を返し、これでも昇順になります。

その逆で実行文の「a – b」を「b – a」で降順つまり大きい順になります。

[3, 4, 5]でも[6, 6, 6]でも小さい順に並び変えればその2番目(items[1])と3番目(items[2])を取り出し足せば最大値が求められます。

ABC207-B

問題「 容器に水色ボールA個ある。そこに水色ボールB個、赤色ボールC個入れる操作を何度か行い、水色ボールが赤色ボールのD倍以下になるようにする。最小回数を求める」

制約A,B,C,Dは正の整数で、10の5乗以下 入力「A B C D」

10の何乗以下の制約をよく見ますがそこまで深く考えなくても今の段階では大丈夫。例1「5 2 3 2」は

初めに   水5個–赤0個で、

1回操作    7個–  3個

2回操作    9個–  6個  9は6の2倍以下なので答えは「2」

こういう流れですが、やはりB問題から難しい…まずACをもらったコードを書きます。

'use strict'
function main(input) {
  const [A,B,C,D] = input.trim().split(" ").map(Number);
  let blue = A;
  let red = 0;
  let answer = -1;
  
  for(let i = 1; i <= A; i++) {
    blue += B;
    red += C;
    if(blue <= red * D) {
      answer = i;
      break;
    }
  }
  console.log(answer);
}
main(require('fs').readFileSync('/dev/stdin', 'utf8'));

まずは入力をA,B,C,Dに振り分けて初めのblueはA個、redは0個、answerはとりあえず出来なかった時用の-1にしておいて達成した際はあとでその回数をanswerに代入しています。この問題のポイントと思うのはfor文の条件の書き方let i = 1;最初の操作は一回目なので1からスタートです。次の「i<=A」は成り立つ式の変更で導きます。

A + Bx <= Cx * D   ←これを実現したい,xは操作回数

A <= Cx * D -Bx

A <= CDx -Bx

A <= (CD -B)x

A / (CD -B) <= x    ←これなら条件満たす

Aを正で割るんだから A / (CD -B) <= A

例えばA / (CD -B)が3.001で切り上げて(3回では達成できないので)x=4回になる。この時Aは必ず4より大きいのか?正の整数を正の整数で割るんだからAは4より大きくなりますよね。というわけで「xはA以下」で今回のコードの式では「i<=A」となります。

blueにB足して、redにC足して、if文で判定D倍以下ならanswerにはその時の回数iが入りbreak;その時点で処理を抜けます。

ABC206-A

問題「消費税率8パーセント、ドリンク1本N円、低下が206円より安い時”Yay!”、定価と同じで”so-so”、定価より高い時“:(”を出力」

制約1≦N≦300、Nは整数 入力「N」なのでNがいくらだったら、206円より上下同等を判定するコードを書きます。これは今までの中でも易しめでは?

'use strict'
function main(input) {
  const N = parseInt(input, 10);
  const price = Math.floor(1.08 * N);

if (price < 206) {
     console.log("Yay!");
  } else if(price === 206) {
     console.log("so-so");
  } else {
     console.log(":(");
  }
}
main(require('fs').readFileSync('/dev/stdin', 'utf8'));

入力をparseIntで数値型にして、1.08を掛けたものをMath.floorで小数点以下切り捨ててpriceとします。そのpriceをif文で判定しAC。ちなみに「Yay!」が「yay」や「Yay」だったり「so-so」が「soso」でも不正解なので気をつけてください。

簡単な例が出たので「コードテスト」の使い方を説明します。問題が書かれている上のメニューにある「コードテスト」を押すと実際に書いたコードを動かせますので便利です。「言語」を選択し、「ソースコード」にVSCodeなどで書いたあなたのコードを貼り付けて「標準入力」に問題ページの「180」などを貼り付けて「実行」を押すと下の「標準出力」に結果「Yay!」が表示されますので、テストが行えます。エラーが出た際は下の「標準エラー出力」に内容が出るので直しの参考に。実行時間も出るのでこの段階で「もっと効率のいい記述はないか?」など検討してもいいでしょう。

初め僕はこの機能を知らずVScodeでhtmlファイルとjsファイルを作ってChromeで表示し、デベロッパーツールのコンソールで確認してました…すっごい時間も掛かるし、今回の様に「N」と一つだけならmain(require(‘fs’).readFileSync(‘/dev/stdin’, ‘utf8’));をコメントアウトしmain(180);で実行できるのですが、入力が複数あるとmain(A,B)としソースコードの方もfunction main(input1, input2)など面倒でした… こんな手間を取らないように皆さんは「コードテスト」使いましょう。

ABC206-B

問題「1日目の朝に1円、2日目の朝に2円、i日目の朝にi円貯金します。N円以上入っていることを初めて確認するのは何日目の夜?」

この前のN!円硬貨に似てますが今回は掛け算ではなく足し算です。入力例1「12」は

1朝1円貯金、1夜1円

2朝2円貯金、2夜3円

3朝3円貯金、3夜6円

4朝4円貯金、4夜10円

5朝5円貯金、5夜15円

なので12円を確認するのは5日目の夜、出力は「5」です。B問題にしては簡単な方でしょう。for文でも書けますが、whileで思いついたので、

function main(input) {
  let N = Number(input.trim());
  let x = 0;
  let i = 1;
  while(x <= N) {
    x += i;
    if (x >= N) {
      console.log(i);
      break;
    }
    i++;
  }
}
main(require('fs').readFileSync('/dev/stdin', 'utf8'));

trimで余白を一応取り除いて、それをNumberで数値化して、貯金額がx円でi日目を用意して「xが目標額Nになるまで」が条件で、xにiを足し上げて、iをインクリメントですが、その間でif文「xが目標額Nを以上になったら」iを出力して終了のbreak

ABC205-B

問題「1以上N以下の長さNの整数A、Aが(1,2,3,…N)の並び替えで作れるか」

問題文だけではよくわかりませんが…例を見れば大丈夫です。なんですが例の入力が変な形…

「5

3 1 2 4 5」

と改行されています。(3,1,2,4,5)を並び替えて(1,2,3,4,5)を作れるかという問題です。これは“Yes”を出力しますが、作れない例2は

「6

3 1 4 1 5 2」

これでは(1,2,3,4,5,6)は作れませんよね。まずこの改行をどう扱うのか?ですがこれも指定した文字で分けて入れるに格納するsplitを使います。「inputを作り直す」というイメージでしょうか、input = input.trim().split(“\n”); (←バックスラッシュは半角で、また\は半角バックスラッシュのこと)これでinputを改行で分けて配列へ移すので例2の場合[‘6’, “3 1 4 1 5 2”]になります。入力を1行にまとめただけですし、文字列のままでは不十分なのでこれをsplit(“ ”)半角空白で分けて数値化します。

'use strict'
function main(input) {
  input = input.trim().split("\n");
  let items = input.map((item)=>item.split(" ").map(Number));
  const N = items[0][0];
  const a = items[1];
  
  let A_set = new Set(a);
  if(N === A_set.size) {
    console.log("Yes");
  } else {
    console.log("No");
  }
}
main(require('fs').readFileSync('/dev/stdin', 'utf8));

全て書きましたが、setの前も重要です。itemsは[[6], [3,1,4,1,5,2]]の状態で、items[0]が[6]なのでitems[0][0]で6になりますが、同じように見えますが”配列そのもの”とその”中身”なので、このデータ型の違いで正解不正解が分かれます。items[1]は[3,1,4,1,5,2]です。

ここから次の大きなポイントのsetに入ります。「JavaScript Setオブジェクト」などで調べてもらうと詳しく出てきます。aをnew SetでA_setに入れてA_setのsize(要素数)がNと同じなら”Yes”、というコードです。3,1,4,1,5,2で6個のような気がしますが、同じものはカウントされないので2度目の1が弾かれてA_set.size(要素数)は5なのでNと異なり”No”になります。同じものを弾くSetオブジェクトの性質を利用した回答でした。

ABC204-B

問題「N本の木がある。i番目の木にはAi個の木の実、リスは木の実が10個以下の木からは取らない、10個以上の木からは10個残して残り全てを収穫、リスが取る木の実の数の合計は?」

アルゴリズムはシンプルですし、入力が前回と同じなのでいい復習になる問題

「N

A1……An」

例1では

「3

6 17 28」

なので1本目は0、2本目は7、3本目は18個取るので合計は「25」になる。

'use strict'
function main(input) {
  input = input.trim().split("\n");
  let items = input.map((item)=>item.split(" ").map(Number));
  const N = items[0][0];
  const a = items[1];

  let ans = 0;
  for(let i = 0; i < N; i++) {
    if(a[i] > 10) {
      ans += (a[i] - 10);
    }
  }
  console.log(ans);
}
main(require('fs').readFileSync('/dev/stdin', 'utf8'));

N=3ですが、a[0]a[1]a[2]までしかないのでi<Nまでループします。aは[6, 17, 28]なのでa[0]は6で10以下なのでスルー、a[1]は17なのでif文が作動し、17-10の7がansに足されます。a[2]は28なので28-10の18がansに足され7+18=25になります。

ABC203-B

問題「1階からN階までのマンション、部屋はK室あり、i階のj号室はi0jで表す。3桁の整数と見なし全てを足した値は?」

制約はN,K共に1~9の整数 入力は「N K」

これも例を見ればイメージしやすい。入力が「1 2」なら101と102の2部屋なので101+102=203です(これがマンション?)

100の位と1の位を分けて計算していくので、初めて登場の2重ループです。

'use strict'
function main(input) {
  let items = input.trim().split(' ').map(Number);
  const N = items[0];
  const K = items[1];
  let ans = 0;
 
  for (let a = 1; a <= N; a++) {
    for (let b = 1; b <= K; b++) {
      ans += (a*100) + b;
    }
  }
  console.log();
}
main(require('fs').readFileSync('/dev/stdin', 'utf8'));

改行がないのでこのままsplit(“ ”)します。a,b共に0階-0号室はないので1からN階・K号室までループします。100の位なので100倍して1の位はそのまま、これを足したものをansに足し上げていきます。2重ループがイメージしづらい場合は1つずつ部屋番号を出していく記述を紹介。

for (let a = 1; a <= N; a++) {
  for (let b = 1; b <= K; b++) {
     console.log((a*100) + b);
  }
}

とすると101,102,103,201,202,203,301,302,303の順で出力されるので、まずa=1の時にbが1で101、bが2で102、bが3で103、次にaが2にインクリメントされ……という流れだと理解しやすいのでは?

コメント

タイトルとURLをコピーしました