LoginSignup
3
2

More than 1 year has passed since last update.

RubyでAtCoder 版!蟻本 (初級編)を解いていく

Last updated at Posted at 2020-02-11

前置き

業務での実装力を高めようと、AtCoderをやっています。
AB問題の過去問を200問以上解いたのですが、なかなか灰コーダーから抜け出せないため、また、C++やPythonで取り組む人に比べるとRubyでAtCoderに取り組んでいる人は少ないかなと思い、初見で解けなかった問題も解説や他の解答見て作ることで残していきます。

引用元: AtCoder 版!蟻本 (初級編)

問題と解答

例題 1-1-1 くじびき


n,m=gets.split.map &:to_i
p=n.times.map{gets.to_i}+[0]
# 半分全列挙
a=p.repeated_combination(2).to_a.map{|i|i.inject(:+)}.sort.reverse
s=0
# 全探索
a.each do |left|
  # 二分探索
  right=a.bsearch{|r|r+left<=m}
  s=[s,left+right].max unless right.nil?
end
puts s
【参考リンク】

例題 1-6-1 三角形

N=gets.to_i
xy=N.times.map{gets.split.map &:to_i}
ans=0
# 全探索 (二重の for 文)
N.times do |i|
  N.times do |j|
    next if i==j
    x1=xy[i][0]
    y1=xy[i][1]
    x2=xy[j][0]
    y2=xy[j][1]
    # 三角形の成立条件
    res=((x1-x2)**2+(y1-y2)**2)**0.5
    ans = [ans, res].max
  end
end
puts ans
k,s=gets.split.map &:to_i
l=k+1
a=0
l.times do |x|
  l.times do |y|   
    z=s-x-y
    a+=1 if z>=0 && z<=k
  end
end
puts a
【別解(微妙かも)】
k,s=gets.split.map &:to_i
puts [*0..k].repeated_permutation(2).map{|a,b|a+b}.select{|i|i<=s}.count{|xy|(z=s-xy)>=0 && z<=k}
N,Y=gets.split.map &:to_i
n=N+1
f=false
n.times do |x|
  n.times do |y|
    break if x+y>N
    z=(Y-10000*x-5000*y)/1000
    if x+y+z===N && z>=0
      puts "#{x} #{y} #{z}"
      f=true
      break
    end
  end
  break if f
end
puts "-1 -1 -1" unless f

例題 1-6-2 Ants (POJ No.1852)**

Atcoder Grand Contest 013 解説 C Ants on a Circle より

ここは分かった。

T 秒後にゼッケンのある座標の集合は、当然、T 秒後に蟻のいる座標の集合と同じなので、T 秒後にそれぞれの蟻がどこにいるかを求めると、番号は分からないが、どの座標にゼッケンがあるのかが分かります。

それ以外は解説読んでもコードに起こせない。

二つの蟻がゼッケンを交換する時、時計回りに歩いてる蟻のゼッケンの番号は 1 増え、反時計回りに歩いている蟻のゼッケンの番号は 1 減ります。ある二つの蟻が T 秒間にすれ違う回数は、O(1)で求めることができます。よって、最初に 1 のゼッケンをつけていた蟻が、T 秒間に他の蟻とすれ違う回数の合計は、O(N) で分かります

例題おまけ 累積和・しゃくとり法

つい先日のABC。頻出らしいのでついでに載せておく。

【累積和解法】
N,K=gets.split.map &:to_i
# 期待値の前計算
p=gets.split.map{|i|(i.to_f+1)*0.5}
# 累積和の計算
a=[0]
N.times do |i|
  a[i+1]=a[i]+p[i]
end
# 隣接するK項の和の計算
s=0
(N-K+1).times do |i|
  r=a[i+K]-a[i]
  s=[s,r].max
end
puts s
【しゃくとり法解法】
N,K=gets.split.map &:to_i
# 期待値の前計算
p=gets.split.map{|i|(i.to_f+1)*0.5}
# しゃくとり法・隣接するK項の和の計算
r=s=p[0..K-1].inject(:+)
(N-K).times do |i|
  r+=p[i+K]-p[i]
  s=[s,r].max
end
puts s
【参考リンク】

例題 2-1-1 部分和問題

【bit全探索解法】
s=gets.chomp.chars
n=s.size
a=0
# bit全探索
['', '+'].repeated_permutation(n-1) do |i|
  t=s.zip(i).join
  if t.include?('+')
    # "+"をto_iすると0になる
    a+=t.split('+').map(&:to_i).inject(:+)
  else
    a+=t.to_i
  end
end
puts a
【再帰関数解法】
# 再帰関数
def calc(s,num)
  if num==0
    arr=s.split('+')
    sum=0
    arr.each do |a|
      sum+=a.to_i
    end
    return sum
  end
  return calc(s.clone.insert(num,'+'),num-1) + calc(s,num-1)
end

s=gets.chomp
num=s.size()

puts calc(s,num-1)

【参考リンク】

例題 2-1-2 深さ優先探索

【再帰関数解法】
# デバッグ用: gridの現在の状態を出力
# def print_grid(grid)
#   grid.each { |row| puts row }
#   puts
# end

def depth_first_search(grid, current_x, current_y, height, width)
  # 迷路の範囲外、壁、訪問済みの場合は探索終了
  return false if current_x < 0 || current_y < 0 || current_x >= width || current_y >= height || grid[current_y][current_x] == "#" || grid[current_y][current_x] == "x"

  # ゴールに到達した場合
  return true if grid[current_y][current_x] == "g"

  # 訪問済みにマーク
  grid[current_y][current_x] = "x"

  # デバッグ用: gridの現在の状態を出力
  # puts "Current grid state:"
  # print_grid(grid)

  # 上下左右のマスを探索
  return depth_first_search(grid, current_x, current_y - 1, height, width) ||
         depth_first_search(grid, current_x, current_y + 1, height, width) ||
         depth_first_search(grid, current_x - 1, current_y, height, width) ||
         depth_first_search(grid, current_x + 1, current_y, height, width)
end

# 入力
height, width = gets.split.map(&:to_i)
# 迷路の状態を表す二次元配列を作成
grid = []
start_x = start_y = 0

height.times do |i|
  row = gets.chomp
  if row.include?("s")
    start_y = i
    start_x = row.index("s")
  end
  grid << row
end

# 深さ優先探索
if depth_first_search(grid, start_x, start_y, height, width)
  puts "Yes"
else
  puts "No"
end
【スタック解法】
# デバッグ用: gridの現在の状態を出力
# def print_grid(grid)
#   grid.each { |row| puts row }
#   puts
# end

def depth_first_search_stack(grid, start_x, start_y, height, width)
  stack = [[start_x, start_y]]

  while !stack.empty?
    x, y = stack.pop

    # 迷路の範囲外、壁、訪問済みの場合はスキップ
    next if x < 0 || y < 0 || x >= width || y >= height || grid[y][x] == "#" || grid[y][x] == "x"

    # ゴールに到達した場合
    return true if grid[y][x] == "g"

    # 訪問済みにマーク
    grid[y][x] = "x"

    # 上下左右のマスをスタックに追加
    stack.push([x, y - 1], [x, y + 1], [x - 1, y], [x + 1, y])

    # デバッグ用: gridの現在の状態を出力
    # puts "Current grid state:"
    # print_grid(grid)

    # デバッグ用: スタックの現在の状態を出力
    # puts "Current stack state: #{stack.inspect}"
    # puts
  end

  false
end

# 入力
height, width = gets.split.map(&:to_i)
grid = []
start_x = start_y = 0

height.times do |i|
  row = gets.chomp
  if row.include?("s")
    start_y = i
    start_x = row.index("s")
  end
  grid << row
end

# 深さ優先探索
if depth_first_search_stack(grid, start_x, start_y, height, width)
  puts "Yes"
else
  puts "No"
end

例題 2-1-3 幅優先探索

# デバッグ用: gridの現在の状態を出力
# def print_grid(grid)
#   grid.each { |row| puts row }
#   puts
# end

def bfs(grid, start_x, start_y, goal_x, goal_y, height, width)
  visited = Array.new(height) { Array.new(width, false) }
  visited[start_y][start_x] = true
  queue = [[start_x, start_y, 0]]

  while !queue.empty?
    x, y, depth = queue.shift

    if x == goal_x && y == goal_y
      return depth
    end

    [[1, 0], [-1, 0], [0, 1], [0, -1]].each do |dx, dy|
      next_x = x + dx
      next_y = y + dy

      if next_x >= 0 && next_y >= 0 && next_x < width && next_y < height && !visited[next_y][next_x] && grid[next_y][next_x] != "#"
        visited[next_y][next_x] = true
        queue.push([next_x, next_y, depth + 1])

        # デバッグ用: gridの現在の状態を出力
        # puts "Visiting (#{next_x}, #{next_y}), depth: #{depth + 1}"
        # print_grid(grid)
        # puts "Queue state: #{queue.inspect}"
        # puts
      end
    end
  end

  -1
end

# 入力
height, width = gets.split.map(&:to_i)
start_y, start_x = gets.split.map(&:to_i).map { |x| x - 1 }
goal_y, goal_x = gets.split.map(&:to_i).map { |x| x - 1 }
grid = height.times.map { gets.chomp }

# 幅優先探索
steps = bfs(grid, start_x, start_y, goal_x, goal_y, height, width)

puts steps

...

...

例題 2-4-3 食物連鎖 (POJ No.1182)

class UnionFind
    def initialize(n)
        @par = [*0...n]
        @rnk = [0]*n
    end

    def find(x)
        return x if @par[x] == x
        @par[x] = find(@par[x])
    end

    def same(x,y)
        find(x) == find(y)
    end

    def unite(x,y)
        x=find(x);y=find(y)
        return if x==y

        if @rnk[x] < @rnk[y]
            @par[x] = y
        elsif
            @par[y] = x
            @rnk[x]+=1 if @rnk[x]==@rnk[y]
        end
    end
end

n,q = gets.chomp.split.map(&:to_i)
u = UnionFind.new(n)

1.upto(q){
    p,a,b = gets.chomp.split.map(&:to_i)
    if p==0
        u.unite(a,b)
    else
        puts u.same(a,b) ? "Yes" : "No"
    end
}
3
2
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
3
2