Edited at

AtCoder に登録したら解くべき精選過去問 10 問を Ruby で解いてみた

More than 1 year has passed since last update.


はじめに

n番煎じですが、@drkenさんの記事 AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~に取り上げられている問題10問を Ruby で解いてみました。

解法の詳細な解説は元記事をご参照ください。


第 1 問: ABC 086 A - Product (100 点)

a, b = gets.strip.split.map(&:to_i)

puts (a * b).odd? ? 'Odd' : 'Even'

一行に複数の数値が入力されるときにそれらを受け取るお決まりのパターンです。また、Ruby には偶数奇数を判定する even?odd? という組み込みのメソッドがあるのでそれを活用しています。

提出例


第 2 問: ABC 081 A - Placing Marbles (100 点)

nums = gets.strip.split('').map(&:to_i)

puts nums.inject(:+)

配列の和を計算するには、Ruby2.4以降では nums.sum でOKですが、ここで判定に用いられているのは 2.3.3 なので従来の inject メソッドを使っています。

提出例


第 3 問: ABC 081 B - Shift Only (200 点)

_n = gets.to_i

as = gets.strip.split.map(&:to_i)
cnt = 0
while as.all?(&:even?) do
cnt = cnt.succ
as = as.map { |a| a / 2 }
end

puts cnt

「すべてが偶数である」という条件の判定に all? という便利メソッドを使っています。また、 Ruby には++のようなインクリメント演算子はなく、cnt += 1 のような記法や cnt.succ, cnt.next のようなメソッドを用います。

提出例


第 4 問: ABC 087 B - Coins (200 点)

a, b, c, x = 4.times.map { gets.to_i }

cnt = 0
(0..a).each do |i|
(0..b).each do |j|
(0..c).each do |k|
cnt += 1 if i * 500 + j * 100 + k * 50 == x
end
end
end

puts cnt

後置 if 記法 cnt += 1 if i * 500 + j * 100 + k * 50 == x を使っています。

提出例


第 5 問: ABC 083 B - Some Sums (200 点)

n, a, b = gets.strip.split.map(&:to_i)

# 各桁の和を求める
def digit_sum n
sum = 0
while n > 0 do
sum += n % 10
n /= 10
end
sum
end

ans = (1..n)
.map { |i| [i, digit_sum(i)] }
.select { |d| a <= d[1] && d[1] <= b }
.inject(0) { |sum, d| sum + d[0] }

puts ans

各桁の和を求める関数は標準的な書き方ですね。

後半では、map, select, inject のような配列操作関数を活用しています。

提出例


第 6 問: ABC 088 B - Card Game for Two (200 点)

_n = gets.to_i

as = gets.strip.split.map(&:to_i).sort.reverse

alice = 0
bob = 0

until as.empty? do
alice += as.shift
break if as.empty?
bob += as.shift
end

puts (alice - bob)

ソートや逆順に並べ替える等の操作が非常に手軽にできるところは Ruby の利点ですね。

他に、 while !条件 と等価な until 記法を用いています。

提出例


第 7 問: ABC 085 B - Kagami Mochi (200 点)

n = gets.to_i

mochis = n.times.map { gets.to_i }
puts mochis.uniq.size

配列から重複を取り除く uniq メソッドを用いて簡単に解くことができました。

提出例


第 8 問: ABC 085 C - Otoshidama (300 点)

n, y = gets.strip.split.map(&:to_i)

_i, _j, _k = -1, -1, -1

(0..n).each do |i|
(0..n-i).each do |j|
if i * 10000 + j * 5000 + (n - i - j) * 1000 == y
_i, _j, _k = i, j, n - i - j
end
end
end

puts "#{_i} #{_j} #{_k}"

結果の出力で、文字列の中に変数の値を埋め込む「変数展開」を活用しています。

提出例


第 9 問: ABC 049 C - Daydream (300 点)

s = gets.strip

words = ['dream', 'dreamer', 'erase', 'eraser']

until s.empty? do
reduced = false
words.map do |w|
if s[-w.size, w.size] == w
s = s[0..-w.size - 1]
reduced = true
break
end
end

unless reduced
puts 'NO'
exit 0
end
end

puts 'YES'

部分文字列を切り出す操作を [] を用いて直感的にできるのは利点ですね。

提出例


第 10 問: ABC 086 C - Traveling (300 点)

n = gets.to_i

data = n.times.map { gets.strip.split.map(&:to_i) }

def is_reachable(current, next_data)
# 次の地点までのマンハッタン距離
tc, xc, yc = current
tn, xn, yn = next_data
dist = (xc - xn).abs + (yc - yn).abs

# 最短距離でも間に合わない
return false if dist > tn - tc

# 行ったり来たりでちょうど到着できるか
(dist - (tn - tc)).even?
end

current = [0, 0, 0]
data.each do |d|
unless is_reachable(current, d)
puts 'No'
exit 0
end
current = d
end

puts 'Yes'

他言語でもわりと見かけますが、最終行にメソッドの返り値を書く場合は return を省略できます。

提出例