START_TIME = Time.utc.to_unix_ms TL = (ENV["TL"]? || 900).to_i PART = (ENV["PART"]? || 100).to_i IC = (ENV["IC"]? || 1).to_f FC = (ENV["FC"]? || 50).to_f N = 45 INF = 1u64 << 62 MED = 500_000_000_000_000_000i64 RND = Random.new(2) alias PI = Tuple(Int32, Int32) macro debug(msg) {% if flag?(:local) %} STDERR.puts({{msg}}) {% end %} end macro debugf(format_string, *args) {% if flag?(:local) %} STDERR.printf({{format_string}}, {{*args}}) {% end %} end def crash(msg, caller_line = __LINE__) puts "[ERROR] line #{caller_line}: #{msg}" exit end macro assert(cond, msg = "", caller_line = __LINE__) {% if flag?(:local) %} if !({{cond}}) crash({{msg}}, {{caller_line}}) end {% end %} end record Pos, y : Int32, x : Int32 class Result property :pairs, :v0, :v1 getter :score @score : Float64 def initialize(@pairs : Array(PI), @v0 : Int64, @v1 : Int64) @score = Math.log10({(@v0 - MED).abs, (@v1 - MED).abs}.max + 1) end def to_s(io) io << "#{@pairs.size}\n" << @pairs.map { |p| {p[0] + 1, p[1] + 1}.join(" ") }.join("\n") end end RES_EMPTY = Result.new([] of PI, 0u64, 0u64) def dist(y0, x0, y1, x1) return (y0 - y1).abs + (x0 - x1).abs end def dist(p0, p1) return (p0.y - p1.y).abs + (p0.x - p1.x).abs end STATE_EMPTY = State.new([INF], 0, [] of Int32, nil, INF) class Solver def initialize(@a : Array(Int64), @b : Array(Int64)) end def solve(timelimit) av = @a.map { |v| v >> 6 } bv = @b.map { |v| v >> 6 } is = N.times.to_a N.times do |i| is.swap(i, RND.rand(N - i) + i) end on = is.first(19) off = is.last(N - on.size) med = MED sum_a = av.sum + on.map { |i| av[i] }.sum sum_b = bv.sum + on.map { |i| bv[i] }.sum cost = (sum_a - med).to_f ** 2 + (sum_b - med).to_f ** 2 best_cost = cost best_on = on.dup debug("initial_cost:#{cost}") turn = 0 cooler = IC init_time = Time.utc.to_unix_ms while true turn += 1 if (turn & 0xFFF) == 0 cur_time = Time.utc.to_unix_ms if cur_time > timelimit debug("turn:#{turn}\n") break end ratio = (cur_time - init_time) / (timelimit - init_time) cooler = (1.0 - ratio) * IC + ratio * FC end on_i = RND.rand(on.size) off_i = RND.rand(off.size) v0 = on[on_i] v1 = off[off_i] na = sum_a - av[v0] + av[v1] nb = sum_b - bv[v0] + bv[v1] nc = (na - med).to_f ** 2 + (nb - med).to_f ** 2 if (accept(nc, cost, cooler)) cost = nc on[on_i] = v1 off[off_i] = v0 sum_a = na sum_b = nb if nc < best_cost debug("best_cost:#{nc} turn:#{turn} diff_a:#{sum_a - med} diff_b:#{sum_b - med}") best_cost = nc best_on = on.dup end end end best_off = N.times.reject { |i| best_on.includes?(i) }.to_a ans = [] of PI la = @a.dup lb = @b.dup (best_off.size // 2).times do |i| i0 = best_off[i * 2] i1 = best_off[i * 2 + 1] ans << {i0, i1} best_on << {i0, i1}.min sa = (la[i0] + la[i1]) // 2 sb = (lb[i0] + lb[i1]) // 2 la[i0] = la[i1] = sa lb[i0] = lb[i1] = sb end while best_on.size > 1 i0 = best_on.shift i1 = best_on.shift ans << {i0, i1} best_on << {i0, i1}.min sa = (la[i0] + la[i1]) // 2 sb = (lb[i0] + lb[i1]) // 2 la[i0] = la[i1] = sa lb[i0] = lb[i1] = sb end return Result.new(ans, la[0], lb[0]) end def accept(new_cost, old_cost, cooler) return true if new_cost <= old_cost diff = new_cost - old_cost v = -diff / old_cost * cooler # debug(v) return false if v < -8 return RND.rand < Math.exp(v) end end def main gets a = [] of Int64 b = [] of Int64 N.times do |i| u, v = read_line.split.map(&.to_i64) a << u b << v end solver = Solver.new(a, b) best_res = Result.new([] of PI, a[0], b[0]) # turn = 0 # while Time.utc.to_unix_ms - START_TIME < TL # res = solver.solve(START_TIME + TL) # if res.cost < best_res.cost # debug("best_cost:#{res.cost} at turn:#{turn}") # best_res = res # end # turn += 1 # end # debug("turn:#{turn}") part = PART part.times do |i| res = solver.solve(START_TIME + TL * (i + 1) // part) if res.score < best_res.score best_res = res end end puts best_res {% if flag?(:local) %} STDERR.printf("raw:%7f score:%d\n", best_res.score, (2000000 - 100000 * best_res.score).floor) {% end %} end main