AOJ(問題集)8
AIZU ONLINE JUDGE: Programming Challenge
0071 Bombs Chain
$<.gets.to_i.times do |co| $<.gets field = Array.new(8) {$<.gets.chomp.chars.map(&:to_i)} xi, yi = $<.gets.to_i, $<.gets.to_i blast = ->(x, y) { field[y][x] = 2 3.times do |i| [[1, 0], [0, -1], [-1, 0], [0, 1]].each do |dx, dy| x1, y1 = x + dx * (i + 1), y + dy * (i + 1) next if x1 < 0 or x1 >= 8 or y1 < 0 or y1 >= 8 if field[y1][x1] == 1 blast.(x1, y1) else field[y1][x1] = 2 end end end } blast.(xi - 1, yi - 1) puts "Data #{co + 1}:" puts field.map {|line| line.map {|a| a == 2 ? 0 : a}.join} end
0072 Carden Lantern
until (n = $<.gets.to_i).zero? m = $<.gets.to_i table = Array.new(m) {$<.gets.split(",").map(&:to_i)} table.map! {|a, b, d| [a, b, d / 100 - 1]} min = Float::INFINITY (1..m).each do |i| table.combination(i) do |paths| next unless paths.inject([]) {|a, path| a + path[0, 2]}.uniq.sort == [*0...n] n = paths.map{|path| path.last}.sum min = n if n < min end end puts min end
まずはこれで解ける筈だが、時間オーバー。そりゃそうだ。
ふと、動的計画法で解けることに気がつく。これはよいアイデア。
until (n = $<.gets.to_i).zero? m = $<.gets.to_i field = [] Array.new(m) {$<.gets.split(",").map(&:to_i)}.each do |a, b, d| n = d / 100 - 1 field += field.map {|spots, num| [(spots + [a, b]).uniq, num + n]} field << [[a, b], n] end puts field.select {|x| x.first.size == n}.sort_by {|x| x.last}.first.last end
しかしメモリオーバー。ならばと訪れた史跡のチェックをビット演算でおこない、さらに配列の代わりにハッシュを使って省メモリ化。
until (n = $<.gets.to_i).zero? m = $<.gets.to_i field = {0=>0} Array.new(m) {$<.gets.split(",").map(&:to_i)}.each do |a, b, d| n = d / 100 - 1 added = {} field.each do |spots, num| spots |= 2 ** a spots |= 2 ** b added[spots] = num + n if !added[spots] or num + n < added[spots] end field.merge!(added) {|spots, b, f| [b, f].min} end puts field[2 ** n - 1] end
それでもメモリオーバー。うーん。いきづまった。
まったく別の考え方で。後戻りも含めて幅優先探索し、適当なところで打ち切る。
until (n = $<.gets.to_i).zero? m = $<.gets.to_i cpd = Hash.new([]) table = Array.new(m) {$<.gets.split(",").map(&:to_i)} table.map! {|a, b, d| [a, b, d / 100 - 1]} table.each_with_index do |x, i| cpd[x[0]] += [i] cpd[x[1]] += [i] end stack = [[0, 1, 0, 0]] # 現在位置、行った場所、灯籠の数、使った道 result = loop do nxt = stack.shift cpd[nxt[0]] .map {|tn| [tn, (table[tn][0, 2] - [nxt[0]]).first, table[tn].last]} .each do |tn, spot, n| nxt_lnt = nxt[2] nxt_lnt += n if (2 ** spot & nxt[1]).zero? stack << [spot, 2 ** spot | nxt[1], nxt_lnt, 2 ** tn | nxt[3]] end tmp1 = stack.select {|x| x[1] == 2 ** n - 1} tmp2 = tmp1 .select {|x| x[3] == 2 ** m - 1} break tmp1 unless tmp2.empty? end puts result.map {|x| x[2]}.min end
今度は時間オーバー。うーむ。
後戻りは一度しかできないとする。
until (n = $<.gets.to_i).zero? m = $<.gets.to_i cpd = Hash.new([]) table = Array.new(m) {$<.gets.split(",").map(&:to_i)} table.map! {|a, b, d| [a, b, d / 100 - 1]} table.each_with_index do |x, i| cpd[x[0]] += [i] cpd[x[1]] += [i] end result = [] stack = [[0, 1, 0, 0, 0]] # 現在位置、行った場所、灯籠の数、使った道、道は戻ったことがあるか while (nxt = stack.shift) if nxt[1] == 2 ** n - 1 result << nxt[2] else cpd[nxt[0]] .map {|path| [path, (table[path][0, 2] - [nxt[0]]).first, table[path].last]} .each do |path, spot, n| next if (nxt[4] & 2 ** path).nonzero? # 道を戻るのは一度だけ # 行った場所で使っていない道だとダメ next if (2 ** spot & nxt[1]).nonzero? and (2 ** path & nxt[3]).zero? nxt_lnt = nxt[2] nxt_lnt += n if (2 ** spot & nxt[1]).zero? f = nxt[4] f |= 2 ** path if (2 ** path & nxt[3]).nonzero? stack << [spot, 2 ** spot | nxt[1], nxt_lnt, 2 ** path | nxt[3], f] end end end puts result.min end
今度も時間オーバー。うーん、ここまで考えたのだが。
またまったく別の方法。すべて足しておいて、削れるだけ削る。
until (n = $<.gets.to_i).zero? m = $<.gets.to_i init_spots = Array.new(m, 0) tourou = 0 table = Array.new(m) {$<.gets.split(",").map(&:to_i)}.map do |a, b, d| init_spots[a] += 1 init_spots[b] += 1 n = d / 100 - 1 tourou += n [a, b, n] end min = tourou solve = ->(i, spots, t) { min = t if t < min return if i == m solve.(i + 1, spots, t) now = table[i] return if spots[now[0]] == 1 or spots[now[1]] == 1 spots[now[0]] -= 1 spots[now[1]] -= 1 solve.(i + 1, spots, t - now[2]) spots[now[0]] += 1 spots[now[1]] += 1 } solve.(0, init_spots, tourou) puts min end
これも時間オーバー。
他の人の回答(参照)。
until (n = $<.gets.chomp.to_i).zero? edges = [] $<.gets.chomp.to_i.times do e = $<.gets.chomp.split(",").map(&:to_i) e[2] = e[2] / 100 - 1 edges << e end #p [n, m, edges] #灯籠の数の少ない順に並べる edges.sort! {|a, b| a[2] <=> b[2]} nodes = Array.new(n, false) connected = [0] nodes[0] = true sumw = 0 while connected.length < n (0...edges.length).each |i| n0, n1, w = edges[i] if nodes[n0] or nodes[n1] if !nodes[n0] nodes[n0] = true connected << n0 sumw += w elsif !nodes[n1] nodes[n1] = true connected << n1 sumw += w end edges.delete_at(i) break end end end puts sumw end
なるほど、灯籠の少ない順にならべておいて、あとは史跡 0 から順に繋げていくと。なるほどなあー。
0073 Surface Area of Quadrangular Pyramid
loop do x, h = $<.gets.to_i, $<.gets.to_i break if x.zero? and h.zero? puts x * x + Math.sqrt(h * h + (x / 2.0) ** 2) * x * 2 end
0074 Videotape
loop do t, h, s = $<.gets.split.map(&:to_i) break if t == -1 and h == -1 and s == -1 left = 120 * 60 - (t * 3600 + h * 60 + s) output = ->(t) { printf "%02d:%02d:%02d\n", h = t / 3600, (m = t / 60) - h * 60, t - m * 60 } output.(left) output.(left * 3) end
0075 BMI
$<.readlines.map {|l| l.split(",").map(&:to_f)} .select {|s, w, h| w / h ** 2 >= 25.0}.each {|a| puts a.first.to_i}
0076 Treasure Hunt II
until (n = $<.gets.to_i) == -1 x, y = 1.0, 0.0 (n - 1).times do l = Math.sqrt(x ** 2 + y ** 2) x, y = x - y / l, y + x / l end puts x, y end
0077 Run Length
$<.readlines.map(&:chomp).each do |given| po = 0 text = "" while po < given.length if given[po] == "@" text += given[po + 2] * given[po + 1].to_i po += 3 else text += given[po] po += 1 end end puts text end
0078 Magic Square
until (n = $<.gets.to_i).zero? square = Array.new(n) {[0] * n} set = ->(i, x, y) { return if i > n * n if x < 0 set.(i, n - 1, y) elsif x >= n set.(i, 0, y) elsif y >= n set.(i, x, 0) elsif square[y][x].nonzero? set.(i, x - 1, y + 1) else square[y][x] = i set.(i + 1, x + 1, y + 1) end } set.(1, n / 2, n / 2 + 1) square.each do |l| puts l.map {|x| sprintf("%4d", x)}.join end end
0079 Area of Polygon
多角形の面積。
include Math points = $<.readlines.map {|l| Complex(*l.split(",").map(&:to_f))} start_po = p0 = points.sort_by(&:imaginary).last prev_arg = PI e = Enumerator.new do |y| loop do points -= [start_po] break if points.empty? selected = points.map {|po| [po - start_po, po]} .reject {|pos| pos.first == 0} .sort_by {|pos| a = pos.first.angle + PI - prev_arg; a >= 0 ? a : a + 2 * PI} .first.last y << selected prev_arg = (selected - start_po).angle + PI start_po = selected end end result = e.each_cons(2).map do |p1, p2| a = (p1 - p0).abs b = (p2 - p0).abs c = (p2 - p1).abs z = (a + b + c) / 2.0 sqrt(z * (z - a) * (z - b) * (z - c)) end.sum puts result
意外とむずかしくて、かなり大袈裟なことをしている。0068 の回答参照。
他の人の回答にはすごいのがあって、例えばどうしてこれで求まるのかわからない。
ああ、そうか、問題をよく読んでいなかったのか! よくあるな、これ。点が順番に並んでいるのだ。じゃあ簡単だ…。
これでOK。
require 'matrix' p0, *points = $<.readlines.map {|l| Vector[*l.split(",").map(&:to_f)]} result = points.each_cons(2).map do |p1, p2| a = (p1 - p0).norm b = (p2 - p0).norm c = (p2 - p1).norm z = (a + b + c) / 2 Math.sqrt(z * (z - a) * (z - b) * (z - c)) end.sum puts result
前の回答だと、点の並びが任意でも計算できる。
0080 Third Root
until (q = $<.gets.to_f) == -1 x = q / 2.0 result = loop do x -= (x ** 3 - q) / (3 * x ** 2) break x if (x ** 3 - q).abs < 0.00001 * q end puts result end