こんにちは!じゃいごテックのあつしです。
今回は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 = 108の時、配列の長さは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を覚えておけばなんとかなります!