こんにちは!じゃいごテックのあつしです。
今回はpaiza Cランクレベルアップメニュー からシミュレーションという問題を解説します。
この問題集はwhileループを使った繰り返し処理に関する2個のSTEP問題(C級)とFINAL問題(C級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!となっています。
STEP問題を解いてみる
STEP問題はwhileループの基本問題です。
簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。
STEP1: 条件を満たす最小の自然数 (paizaランク C 相当)
STEP1 は10000以上かつ13で割り切れる最小の自然数を求める問題です。
解答例
# [解答例1] num = 10000 div = 13 while num % div != 0 num += 1 end puts num # [解答例2] num = 10000 div = 13 puts num + (div - (num % div))
解答例1: 10000から13で割り切れるかを調べて割り切れなければ +1 の処理を繰り返し、割り切れれば繰り返しを抜けて、その数を出力しています。
解答例2: 計算で解答を求めています。
STEP2: シミュレーションの練習 (paizaランク C 相当)
STEP2 は与えられた2つの式で数値を増加させ、整数n
を超えるまでに計算した回数を出力する問題です。
解答例
<<EOS 入力例1 6 3 2 出力例1 2 入力例2 10 2 3 出力例2 3 EOS n = gets.to_i a, b = gets.split.map(&:to_i) paiza = kyoko = 1 count = 0 while true count += 1 kyoko += paiza * a break if kyoko > n paiza += kyoko % b end puts count
解答例: while true
で無限ループを設定して、計算回数count
を+1しながら指定された2つの式(kyoko = kyoko + paiza * a, paiza = paiza + kyoko % b)
を実行します。そして1つ目の式の計算で条件を満たしたときにループを抜けて計算回数count
を表示しています。
シミュレーション (paizaランク C 相当) を解いてみる
※ paiza Cランクレベルアップメニューより
問題
カウンター魔法を得意とするパイザ君は、同じくカウンター魔法を使うモンスターと戦っています。バトルはターン制で、パイザ君が先攻で、パイザ君とモンスターで交互に魔法を使い合います。パイザ君の魔法は 1 回目と 2 回目に使うときにはダメージ 1 ですが、 3 回目以降の n 回目には、(モンスターから受けた (n - 1) 回目の攻撃のダメージ) + (モンスターから受けた (n - 2) 回目の攻撃のダメージ) のダメージを与えます。モンスターの魔法はこれよりも強力で、 1 回目と 2 回目には同じくダメージ 1 ですが、 3 回目以降の n 回目には、 (パイザ君から受けた (n - 1) 回目の攻撃のダメージ) * 2 + (パイザ君から受けた (n - 2) 回目の攻撃のダメージ) のダメージを与えます。
パイザ君は自分がどれくらいモンスターの攻撃を耐えられるか知りたいと思っています。パイザ君の体力を H として、両者が同じ魔法を使い続けたとき、モンスターの何回目の攻撃でパイザ君の体力が 0 以下になるかを出力してください。
入力される値
入力は以下のフォーマットで与えられます。
H
1 行目にパイザ君の体力を表す整数 H が与えられます。
入力値最終行の末尾に改行が1つ入ります。
期待する出力
モンスターの何回目の攻撃でパイザ君の体力が 0 以下になるかを出力してください。
末尾に改行を入れ、余計な文字、空行を含んではいけません。
条件
すべてのテストケースにおいて、以下の条件をみたします。
- 3 ≤ H ≤ 10^8
- 入力例1
- 7
- 出力例1
- 4
- 入力例2
- 35
- 出力例2
- 6
攻略ポイント
ポイント
問題を解く流れ
入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。
INPUT1 = <<~"EOS" 7 EOS OUTPUT1 = <<~"EOS" 4 EOS INPUT2 = <<~"EOS" 35 EOS OUTPUT2 = <<~"EOS" 6 EOS # 確認用コード p INPUT1 # > "7\n" p INPUT2 # > "35\n"
続いて問題を解くメソッド( solve
メソッドとします)を変数の下に定義します。
まず、入力データを受け取る処理を書きます。
def solve(input_line) # 標準入力の受け取り h = input_line.to_i end # 確認用コード p solve(INPUT1) # > 7 p solve(INPUT2) # > 35
データが正しく受け取れていれば、
solve
メソッドにパイザ君とモンスターのダメージ計算を行う処理を追加していきます。
- モンスターの攻撃が終わった時点で判定を行うので、無限ループではなくwhileに終了条件を設定します。
- n-1回目、n-2回目の攻撃を参照するために配列を使います。
def solve(input_line) # 標準入力の受け取り h = input_line.to_i # 初期設定 count = 0 paiza, monster = [], [] # h が0を超えている間繰り返す while h > 0 count += 1 # 2回目までは攻撃は1 if count < 3 paiza << 1 monster << 1 else # [count - 2] -> 前回の攻撃 # [count - 3] -> 前々回の攻撃 paiza << monster[count - 2] + monster[count - 3] monster << paiza[count - 2] * 2 + paiza[count - 3] end # [count - 1] -> 今回の攻撃 h -= monster[count - 1] end count end # 確認用コード p solve(INPUT1) # > 4 p solve(INPUT1) == OUTPUT1 # > false p solve(INPUT2) # > 6 p solve(INPUT2) == OUTPUT2 # > false
あとは出力形式を整えれば完成です。
p solve(INPUT1)
を puts solve(STDIN.read)
に変更すれば正解となりますが、p solve(INPUT1) == OUTPUT1 -> true
を確認するために、結果を改行区切りの文字列に変換して、末尾にも改行を加えます。
def solve(input_line) # 標準入力の受け取り h = input_line.to_i # 初期設定 count = 0 paiza, monster = [], [] # h が0を超えている間繰り返す while h > 0 count += 1 # 2回目までは攻撃は1 if count <= 2 paiza << 1 monster << 1 else # [count - 2] -> 前回の攻撃 # [count - 3] -> 前々回の攻撃 paiza << monster[count - 2] + monster[count - 3] monster << paiza[count - 2] * 2 + paiza[count - 3] end # [count - 1] -> 今回の攻撃 h -= monster[count - 1] end # 文字列型に変換して末尾に改行を追加する count.to_s << "\n" end # 確認用コード p solve(INPUT1) # > "4\n" p solve(INPUT1) == OUTPUT1 # > true p solve(INPUT2) # > "6\n" p solve(INPUT2) == OUTPUT2 # > true
確認用コードを標準入力からのデータ受け取りに変更して、動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read
を使います。(入力終了は Ctrl+D
又は Ctrl+Z
)
解答コード
def solve(input_line) # 標準入力の受け取り h = input_line.to_i # 初期設定 count = 0 paiza, monster = [], [] # h が0を超えている間繰り返す while h > 0 count += 1 # 2回目までは攻撃は1 if count < 3 paiza << 1 monster << 1 else # [count - 2] -> 前回の攻撃 # [count - 3] -> 前々回の攻撃 paiza << monster[count - 2] + monster[count - 3] monster << paiza[count - 2] * 2 + paiza[count - 3] end # [count - 1] -> 今回の攻撃 h -= monster[count - 1] end # 文字列型に変換して末尾に改行を追加する count.to_s << "\n" end puts solve(STDIN.read)
他の解答例
- 2回目までは 攻撃が 1 で
h > 3
がわかっているので、2回目で初期化する。 - 2回分の攻撃履歴があれば良いので配列の長さを2で固定する。
※ 提出解答例だと、条件最大h = 10
8
の時、配列の長さは29になる
def solve(input_line) h = input_line.to_i # 2回目で初期化 count = 2 paiza, monster = [1, 1], [1, 1] h -= 2 while h > 0 count += 1 # paiza[1], monster[1] -> 前回の攻撃 # paiza[0], monster[0] -> 前々回の攻撃 p_atack = monster[1] + monster[0] m_atack = paiza[1] * 2 + paiza[0] # paiza, monster を上書きする paiza = [paiza[1], p_atack] monster = [monster[1], m_atack] h -= m_atack end count.to_s << "\n" end puts solve(STDIN.read)
今回のまとめ
- 繰り返し回数が分からない時は
whileループ
を使うと便利です。 - 終了条件を間違えて無限ループに入ってしまったらctrl+Cでプログラムを強制終了しましょう。
whileに似たループ処理でuntilやloop doもありますが、とりあえずwhileを覚えておけばなんとかなります!